From ae381180e058b6c9c899b045b66d90b3d9165b55 Mon Sep 17 00:00:00 2001 From: luis medina Date: Fri, 31 May 2024 16:01:12 -0500 Subject: [PATCH] yaperos codechallenge java --- .gitignore | 33 +++ microservices/.gitignore | 32 +++ microservices/Readme.md | 38 +++ microservices/antifraud-ms/.gitignore | 33 +++ .../.mvn/wrapper/maven-wrapper.properties | 18 ++ microservices/antifraud-ms/mvnw | 250 ++++++++++++++++++ microservices/antifraud-ms/mvnw.cmd | 146 ++++++++++ microservices/antifraud-ms/pom.xml | 73 +++++ .../antifraud/AntifraudMsApplication.java | 15 ++ .../port/in/ReviewAntiFraudCommand.java | 21 ++ .../port/out/TransactionEdaRepository.java | 7 + .../usecase/ReviewAntiFraudUseCase.java | 39 +++ .../yape/antifraud/config/KafkaConfig.java | 52 ++++ .../antifraud/config/exception/ErrorCode.java | 33 +++ .../config/exception/ErrorHandler.java | 40 +++ .../config/exception/ErrorResponse.java | 15 ++ .../config/exception/GenericException.java | 14 + .../config/exception/KafkaException.java | 7 + .../properties/ConsumerKafkaProperties.java | 8 + .../config/properties/KafkaProperties.java | 13 + .../SpringConfigurationProperties.java | 16 ++ .../config/properties/TopicProperties.java | 12 + .../domain/TransactionStatusEnum.java | 7 + .../kafka/AntiFraudListenerAdapter.java | 41 +++ .../kafka/model/TransactionInModel.java | 23 ++ .../kafka/TransactionKafkaAdapter.java | 32 +++ .../kafka/model/TransactionOutModel.java | 20 ++ .../src/main/resources/application.properties | 14 + .../AntifraudMsApplicationTests.java | 13 + microservices/pom.xml | 38 +++ microservices/transaction-bff/.gitignore | 33 +++ .../.mvn/wrapper/maven-wrapper.properties | 18 ++ microservices/transaction-bff/mvnw | 250 ++++++++++++++++++ microservices/transaction-bff/mvnw.cmd | 146 ++++++++++ microservices/transaction-bff/pom.xml | 99 +++++++ .../TransactionBffApplication.java | 16 ++ .../application/domain/Transaction.java | 22 ++ .../port/in/GetTransactionQuery.java | 11 + .../port/in/RegisterTransactionCommand.java | 21 ++ .../port/out/TransactionClient.java | 30 +++ .../usecase/GetTransactionUseCase.java | 31 +++ .../usecase/RegisterTransactionUseCase.java | 30 +++ .../transactionbff/config/FeignConfig.java | 14 + .../transactionbff/config/GraphQlConfig.java | 18 ++ .../config/exception/CustomErrorDecoder.java | 18 ++ .../exception/CustomExceptionResolver.java | 22 ++ .../config/exception/ErrorHandler.java | 44 +++ .../config/exception/ErrorResponse.java | 12 + .../config/exception/NotFoundException.java | 8 + .../graphql/TransactionControllerGraph.java | 51 ++++ .../model/TransactionCreateModelResponse.java | 40 +++ .../model/TransactionModelRequest.java | 20 ++ .../model/TransactionModelResponse.java | 44 +++ .../graphql/model/TransactionStatus.java | 18 ++ .../graphql/model/TransactionType.java | 18 ++ .../src/main/resources/application.properties | 5 + .../main/resources/graphql/schema.graphqls | 43 +++ .../src/main/resources/static/favicon.ico | Bin 0 -> 14632 bytes .../TransactionBffApplicationTests.java | 13 + microservices/transaction-ms/.gitignore | 33 +++ .../.mvn/wrapper/maven-wrapper.properties | 18 ++ microservices/transaction-ms/mvnw | 250 ++++++++++++++++++ microservices/transaction-ms/mvnw.cmd | 146 ++++++++++ microservices/transaction-ms/pom.xml | 90 +++++++ .../transaction/TransactionMsApplication.java | 13 + .../exception/NotFoundException.java | 7 + .../port/in/GetTransactionQuery.java | 12 + .../port/in/RegisterTransactionCommand.java | 21 ++ .../port/in/UpdateTransactionCommand.java | 19 ++ .../port/out/AntiFraudEdaRepository.java | 7 + .../application/port/out/CacheRepository.java | 12 + .../port/out/TransactionRepository.java | 25 ++ .../usecase/GetTransactionUseCase.java | 34 +++ .../usecase/RegisterTransactionUseCase.java | 49 ++++ .../usecase/UpdateTransactionUseCase.java | 34 +++ .../yape/transaction/config/CacheConfig.java | 10 + .../yape/transaction/config/KafkaConfig.java | 53 ++++ .../config/exception/ErrorCode.java | 26 ++ .../config/exception/ErrorHandler.java | 47 ++++ .../config/exception/ErrorResponse.java | 15 ++ .../config/exception/GenericException.java | 15 ++ .../config/exception/KafkaException.java | 7 + .../properties/ConsumerKafkaProperties.java | 8 + .../config/properties/KafkaProperties.java | 13 + .../SpringConfigurationProperties.java | 16 ++ .../config/properties/TopicProperties.java | 12 + .../yape/transaction/domain/Transaction.java | 29 ++ .../domain/type/TransactionStatusEnum.java | 7 + .../domain/type/TransactionTypeEnum.java | 7 + .../domain/type/TransferTypeEnum.java | 18 ++ .../kafka/TransactionListenerAdapter.java | 40 +++ .../kafka/model/TransactionInModel.java | 21 ++ .../rest/TransactionControllerAdapter.java | 58 ++++ .../rest/model/TransactionModelRequest.java | 20 ++ .../rest/model/TransactionModelResponse.java | 34 +++ .../adapter/rest/model/TransactionStatus.java | 17 ++ .../adapter/rest/model/TransactionType.java | 17 ++ .../adapter/cache/CacheRepositoryAdapter.java | 39 +++ .../data/model/TransactionDataModel.java | 60 +++++ .../adapter/kafka/AntiFraudKafkaAdapter.java | 37 +++ .../kafka/model/TransactionOutModel.java | 33 +++ .../src/main/resources/application.properties | 21 ++ .../TransactionMsApplicationTests.java | 13 + script-bd/script-create_table.sql | 18 ++ 104 files changed, 3679 insertions(+) create mode 100644 .gitignore create mode 100644 microservices/.gitignore create mode 100644 microservices/Readme.md create mode 100644 microservices/antifraud-ms/.gitignore create mode 100644 microservices/antifraud-ms/.mvn/wrapper/maven-wrapper.properties create mode 100644 microservices/antifraud-ms/mvnw create mode 100644 microservices/antifraud-ms/mvnw.cmd create mode 100644 microservices/antifraud-ms/pom.xml create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/AntifraudMsApplication.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/in/ReviewAntiFraudCommand.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/out/TransactionEdaRepository.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/usecase/ReviewAntiFraudUseCase.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/KafkaConfig.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorCode.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorHandler.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorResponse.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/GenericException.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/KafkaException.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/ConsumerKafkaProperties.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/KafkaProperties.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/SpringConfigurationProperties.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/TopicProperties.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/domain/TransactionStatusEnum.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/AntiFraudListenerAdapter.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/model/TransactionInModel.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/TransactionKafkaAdapter.java create mode 100644 microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/model/TransactionOutModel.java create mode 100644 microservices/antifraud-ms/src/main/resources/application.properties create mode 100644 microservices/antifraud-ms/src/test/java/com/yape/antifraud/AntifraudMsApplicationTests.java create mode 100644 microservices/pom.xml create mode 100644 microservices/transaction-bff/.gitignore create mode 100644 microservices/transaction-bff/.mvn/wrapper/maven-wrapper.properties create mode 100644 microservices/transaction-bff/mvnw create mode 100644 microservices/transaction-bff/mvnw.cmd create mode 100644 microservices/transaction-bff/pom.xml create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/TransactionBffApplication.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/domain/Transaction.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/GetTransactionQuery.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/RegisterTransactionCommand.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/out/TransactionClient.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/GetTransactionUseCase.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/RegisterTransactionUseCase.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/FeignConfig.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/GraphQlConfig.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomErrorDecoder.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomExceptionResolver.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorHandler.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorResponse.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/NotFoundException.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/TransactionControllerGraph.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionCreateModelResponse.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelRequest.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelResponse.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionStatus.java create mode 100644 microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionType.java create mode 100644 microservices/transaction-bff/src/main/resources/application.properties create mode 100644 microservices/transaction-bff/src/main/resources/graphql/schema.graphqls create mode 100644 microservices/transaction-bff/src/main/resources/static/favicon.ico create mode 100644 microservices/transaction-bff/src/test/java/com/yape/transactionbff/TransactionBffApplicationTests.java create mode 100644 microservices/transaction-ms/.gitignore create mode 100644 microservices/transaction-ms/.mvn/wrapper/maven-wrapper.properties create mode 100644 microservices/transaction-ms/mvnw create mode 100644 microservices/transaction-ms/mvnw.cmd create mode 100644 microservices/transaction-ms/pom.xml create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/TransactionMsApplication.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/exception/NotFoundException.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/GetTransactionQuery.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/RegisterTransactionCommand.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionCommand.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/AntiFraudEdaRepository.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/CacheRepository.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/TransactionRepository.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/GetTransactionUseCase.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/RegisterTransactionUseCase.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/UpdateTransactionUseCase.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/CacheConfig.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/KafkaConfig.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorCode.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorHandler.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorResponse.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/GenericException.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/KafkaException.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/ConsumerKafkaProperties.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/KafkaProperties.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/SpringConfigurationProperties.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/TopicProperties.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/domain/Transaction.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionStatusEnum.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionTypeEnum.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransferTypeEnum.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/TransactionListenerAdapter.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/model/TransactionInModel.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/TransactionControllerAdapter.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelRequest.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelResponse.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionStatus.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionType.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/cache/CacheRepositoryAdapter.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/data/model/TransactionDataModel.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/AntiFraudKafkaAdapter.java create mode 100644 microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/model/TransactionOutModel.java create mode 100644 microservices/transaction-ms/src/main/resources/application.properties create mode 100644 microservices/transaction-ms/src/test/java/com/yape/transaction/TransactionMsApplicationTests.java create mode 100644 script-bd/script-create_table.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices/.gitignore b/microservices/.gitignore new file mode 100644 index 0000000..7ed0d6b --- /dev/null +++ b/microservices/.gitignore @@ -0,0 +1,32 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices/Readme.md b/microservices/Readme.md new file mode 100644 index 0000000..c448448 --- /dev/null +++ b/microservices/Readme.md @@ -0,0 +1,38 @@ +# Getting Started + +Cada microservicio se ha construido siguiendo el patrón de diseño "Arquitectura Hexagonal" + +### Microservicios + +* **transaction-bff:** Backend for FrontEnd, usando GraphQL. +* **transaction-ms:** Creación, actualización y consulta de transacciones. + * Envía peticiones asíncronas hacia el microservicio antifraud-ms(Evaluación de la transacción), mediante Apache Kafka + * Recibe peticiones asíncronas con el estado final de la transacción, mediante Apache Kafka +* **antifraud-ms:** Analiza la transacción recibida y setea el correcto estado de la ella. + +### Instalación + +Seguir los siguientes pasos. + +1. Situarse en el file \app-java-codechallenge\docker-compose.yml y ejectuar: docker compose up +2. Crear Base de datos: yapebd +3. Ejecutar el siguiente script sql: app-java-codechallenge\script-bd **script-create_table.sql** +3. Crear los siguientes tópicos: + * kafka-console-producer --broker-list localhost:9092 --topic **topic-transaction-update** + * kafka-console-producer --broker-list localhost:9092 --topic **topic-antifraud-review** +4. Ejecutar los microservicios: + * transaction-ms + * antifraud-ms + * transaction-bff +5. Abrir navegador y escribir: http://localhost:8090/graphiql?path=/graphql +6. Existen dos llamados de recursos: + * **(Mutation) register**: Proceso de registro y evaluación de transacciones + * **(Query) getTransactionByCode**: Busca el transaction según el code + * **(Query) getAllTransactions**: Consigue todas las transacciones +7. En el PR se adjuntan pantallas de peticiones y resultados esperados + + + + + + diff --git a/microservices/antifraud-ms/.gitignore b/microservices/antifraud-ms/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/microservices/antifraud-ms/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices/antifraud-ms/.mvn/wrapper/maven-wrapper.properties b/microservices/antifraud-ms/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..aeccdfd --- /dev/null +++ b/microservices/antifraud-ms/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.1 +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip diff --git a/microservices/antifraud-ms/mvnw b/microservices/antifraud-ms/mvnw new file mode 100644 index 0000000..ba9212a --- /dev/null +++ b/microservices/antifraud-ms/mvnw @@ -0,0 +1,250 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.1 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl="${value-}" ;; + distributionSha256Sum) distributionSha256Sum="${value-}" ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/microservices/antifraud-ms/mvnw.cmd b/microservices/antifraud-ms/mvnw.cmd new file mode 100644 index 0000000..406932d --- /dev/null +++ b/microservices/antifraud-ms/mvnw.cmd @@ -0,0 +1,146 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.1 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/microservices/antifraud-ms/pom.xml b/microservices/antifraud-ms/pom.xml new file mode 100644 index 0000000..22d3648 --- /dev/null +++ b/microservices/antifraud-ms/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.yape + microservices + 1.0.0-SNAPSHOT + + + antifraud-ms + 0.0.1-SNAPSHOT + antifraud-ms + Antifraud-ms for Spring Boot + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.kafka + spring-kafka + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.kafka + spring-kafka-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/AntifraudMsApplication.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/AntifraudMsApplication.java new file mode 100644 index 0000000..ce9f3ad --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/AntifraudMsApplication.java @@ -0,0 +1,15 @@ +package com.yape.antifraud; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class AntifraudMsApplication { + + public static void main(String[] args) { + + SpringApplication.run(AntifraudMsApplication.class, args); + } + +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/in/ReviewAntiFraudCommand.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/in/ReviewAntiFraudCommand.java new file mode 100644 index 0000000..57eb743 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/in/ReviewAntiFraudCommand.java @@ -0,0 +1,21 @@ +package com.yape.antifraud.application.port.in; + +import com.yape.antifraud.domain.TransactionStatusEnum; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; +import java.util.UUID; + +public interface ReviewAntiFraudCommand { + void execute(Command command); + @Value + @Builder + class Command { + Long id; + UUID code; + TransactionStatusEnum status; + BigDecimal value; + } + +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/out/TransactionEdaRepository.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/out/TransactionEdaRepository.java new file mode 100644 index 0000000..b6f2a6a --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/port/out/TransactionEdaRepository.java @@ -0,0 +1,7 @@ +package com.yape.antifraud.application.port.out; + +import com.yape.antifraud.infra.out.adapter.kafka.model.TransactionOutModel; + +public interface TransactionEdaRepository { + void updateTransaction(TransactionOutModel transactionModel); +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/usecase/ReviewAntiFraudUseCase.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/usecase/ReviewAntiFraudUseCase.java new file mode 100644 index 0000000..0049265 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/application/usecase/ReviewAntiFraudUseCase.java @@ -0,0 +1,39 @@ +package com.yape.antifraud.application.usecase; + +import com.yape.antifraud.application.port.in.ReviewAntiFraudCommand; +import com.yape.antifraud.application.port.out.TransactionEdaRepository; +import com.yape.antifraud.domain.TransactionStatusEnum; +import com.yape.antifraud.infra.out.adapter.kafka.model.TransactionOutModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +@Slf4j +public class ReviewAntiFraudUseCase implements ReviewAntiFraudCommand { + + @Value("${antifraud.transaction.max.value}") + private BigDecimal maxValue; + private final TransactionEdaRepository transactionEdaRepository; + + public ReviewAntiFraudUseCase(TransactionEdaRepository transactionEdaRepository) { + this.transactionEdaRepository = transactionEdaRepository; + } + + @Override + public void execute(Command command) { + log.info("Reviewing transaction id: {}", command.getId()); + TransactionOutModel transactionModel = TransactionOutModel.builder() + .id(command.getId()) + .status(command.getStatus()) + .code(command.getCode()) + .build(); + transactionModel.setStatus(TransactionStatusEnum.APPROVED); + if (command.getValue().compareTo(maxValue) > 0) { + transactionModel.setStatus(TransactionStatusEnum.REJECTED); + } + this.transactionEdaRepository.updateTransaction(transactionModel); + } +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/KafkaConfig.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/KafkaConfig.java new file mode 100644 index 0000000..b304cb1 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/KafkaConfig.java @@ -0,0 +1,52 @@ +package com.yape.antifraud.config; + +import com.yape.antifraud.config.properties.SpringConfigurationProperties; +import com.yape.antifraud.infra.in.adapter.kafka.model.TransactionInModel; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer; +import org.springframework.kafka.support.serializer.JsonDeserializer; + +import java.util.HashMap; +import java.util.Map; + +@EnableKafka +@Configuration +@Slf4j +public class KafkaConfig { + + private final SpringConfigurationProperties springConfigurationProperties; + private static final String EARLIEST = "earliest"; + public KafkaConfig(SpringConfigurationProperties springConfigurationProperties) { + this.springConfigurationProperties = springConfigurationProperties; + } + + @Bean + public ConsumerFactory antifraudConsumerConfigs() { + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, springConfigurationProperties.getKafka().getBootstrapServers()); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class); + props.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class); + props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, StringDeserializer.class); + props.put(ConsumerConfig.GROUP_ID_CONFIG, springConfigurationProperties.getKafka().getConsumer().getGroupId()); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, EARLIEST); + return new DefaultKafkaConsumerFactory<>(props, new ErrorHandlingDeserializer<>(), new ErrorHandlingDeserializer<>(new JsonDeserializer<>(TransactionInModel.class))); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory antifraudKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory + factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(antifraudConsumerConfigs()); + return factory; + } + +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorCode.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorCode.java new file mode 100644 index 0000000..e8ac356 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorCode.java @@ -0,0 +1,33 @@ +package com.yape.antifraud.config.exception; + +public enum ErrorCode { + + BAD_REQUEST(105, "La request esta mal formateada", "BAD_REQUEST"), + INVALID_PARAMETERS_ERROR(110, "{}", "INVALID_PARAMETERS"), + WEB_CLIENT_GENERIC(103, "Unexpected rest client error", "INTERNAL_SERVER_ERROR"), + KAFKA_EXCEPTION(109, "Error interno de kafka", "KAFKA_EXCEPTION"), + INTERNAL_ERROR(108,"Internal Error","INTERNAL_ERROR"), + INVALID_FILTERS_ERROR(109, "Invalid filters", "INVALID_FILTERS"); + + private final int value; + private final String reasonPhrase; + private final String code; + + ErrorCode(int value, String reasonPhrase, String code) { + this.value = value; + this.reasonPhrase = reasonPhrase; + this.code = code; + } + + public int value() { + return this.value; + } + + public String getReasonPhrase() { + return this.reasonPhrase; + } + + public String getCode() { + return this.code; + } +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorHandler.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorHandler.java new file mode 100644 index 0000000..50ceddd --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorHandler.java @@ -0,0 +1,40 @@ +package com.yape.antifraud.config.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@Slf4j +@ControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(Throwable.class) + public ResponseEntity handle(Throwable ex) { + log.error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), ex); + return buildResponseError(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.INTERNAL_ERROR); + } + + @ExceptionHandler(KafkaException.class) + public ResponseEntity handle(KafkaException ex) { + log.error(ex.getCode().getCode(), ex); + return buildResponseError(HttpStatus.SERVICE_UNAVAILABLE, ex.getCode()); + } + + private ResponseEntity buildCustomResponseError(HttpStatus httpStatus, ErrorCode errorCode, String customDescription) { + final var response = ErrorResponse.builder() + .errorInternalCode(errorCode.value()) + .errorDescription(customDescription) + .errorCode(errorCode.getCode()) + .build(); + + return new ResponseEntity<>(response, httpStatus); + } + + private ResponseEntity buildResponseError(HttpStatus httpStatus, ErrorCode errorCode) { + return buildCustomResponseError(httpStatus, errorCode, errorCode.getReasonPhrase()); + } + +} + diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorResponse.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorResponse.java new file mode 100644 index 0000000..c667142 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/ErrorResponse.java @@ -0,0 +1,15 @@ +package com.yape.antifraud.config.exception; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; + +@Builder +public class ErrorResponse { + @JsonProperty + int errorInternalCode; + @JsonProperty + String errorDescription; + @JsonProperty + String errorCode; +} + diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/GenericException.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/GenericException.java new file mode 100644 index 0000000..1efaeec --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/GenericException.java @@ -0,0 +1,14 @@ +package com.yape.antifraud.config.exception; + +public abstract class GenericException extends RuntimeException { + private final ErrorCode errorCode; + + public GenericException(ErrorCode errorCode) { + super(errorCode.getReasonPhrase()); + this.errorCode = errorCode; + } + + public ErrorCode getCode() { + return this.errorCode; + } +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/KafkaException.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/KafkaException.java new file mode 100644 index 0000000..daf3b07 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/exception/KafkaException.java @@ -0,0 +1,7 @@ +package com.yape.antifraud.config.exception; + +public class KafkaException extends GenericException { + public KafkaException(ErrorCode ec){ + super(ec); + } +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/ConsumerKafkaProperties.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/ConsumerKafkaProperties.java new file mode 100644 index 0000000..ec59d7f --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/ConsumerKafkaProperties.java @@ -0,0 +1,8 @@ +package com.yape.antifraud.config.properties; + +import lombok.Data; + +@Data +public class ConsumerKafkaProperties { + private String groupId; +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/KafkaProperties.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/KafkaProperties.java new file mode 100644 index 0000000..5337738 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/KafkaProperties.java @@ -0,0 +1,13 @@ +package com.yape.antifraud.config.properties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@AllArgsConstructor +@NoArgsConstructor +public class KafkaProperties { + private String bootstrapServers; + private TopicProperties topic; + private ConsumerKafkaProperties consumer; +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/SpringConfigurationProperties.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/SpringConfigurationProperties.java new file mode 100644 index 0000000..c33ddd8 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/SpringConfigurationProperties.java @@ -0,0 +1,16 @@ +package com.yape.antifraud.config.properties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "spring") +@AllArgsConstructor +@NoArgsConstructor +@Data +public class SpringConfigurationProperties { + private KafkaProperties kafka; +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/TopicProperties.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/TopicProperties.java new file mode 100644 index 0000000..454f54a --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/config/properties/TopicProperties.java @@ -0,0 +1,12 @@ +package com.yape.antifraud.config.properties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@AllArgsConstructor +@NoArgsConstructor +public class TopicProperties { + private String antifraud; + private String transaction; +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/domain/TransactionStatusEnum.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/domain/TransactionStatusEnum.java new file mode 100644 index 0000000..aa9502b --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/domain/TransactionStatusEnum.java @@ -0,0 +1,7 @@ +package com.yape.antifraud.domain; + +public enum TransactionStatusEnum { + PENDING, + APPROVED, + REJECTED +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/AntiFraudListenerAdapter.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/AntiFraudListenerAdapter.java new file mode 100644 index 0000000..f84f991 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/AntiFraudListenerAdapter.java @@ -0,0 +1,41 @@ +package com.yape.antifraud.infra.in.adapter.kafka; + +import com.yape.antifraud.application.port.in.ReviewAntiFraudCommand; +import com.yape.antifraud.config.exception.ErrorCode; +import com.yape.antifraud.infra.in.adapter.kafka.model.TransactionInModel; +import com.yape.antifraud.config.exception.KafkaException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class AntiFraudListenerAdapter { + private final ReviewAntiFraudCommand reviewAntiFraudCommand; + + public AntiFraudListenerAdapter(ReviewAntiFraudCommand reviewAntiFraudCommand) { + this.reviewAntiFraudCommand = reviewAntiFraudCommand; + } + + @KafkaListener( + topics = "${spring.kafka.topic.antifraud}", + groupId = "${spring.kafka.consumer.group-id}", + containerFactory = "antifraudKafkaListenerContainerFactory" + + ) + public void listen(TransactionInModel message) { + log.info("AntiFraudListenerAdapter message received from kafka {}", message); + try { + ReviewAntiFraudCommand.Command command = ReviewAntiFraudCommand.Command.builder() + .id(message.getId()) + .code(message.getCode()) + .status(message.getStatus()) + .value(message.getValue()) + .build(); + this.reviewAntiFraudCommand.execute(command); + } catch (Exception ex) { + log.error("Occurred an error in kafka ", ex); + throw new KafkaException(ErrorCode.KAFKA_EXCEPTION); + } + } +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/model/TransactionInModel.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/model/TransactionInModel.java new file mode 100644 index 0000000..eae2559 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/in/adapter/kafka/model/TransactionInModel.java @@ -0,0 +1,23 @@ +package com.yape.antifraud.infra.in.adapter.kafka.model; + +import com.yape.antifraud.domain.TransactionStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.UUID; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class TransactionInModel implements Serializable { + private Long id; + private UUID code; + private TransactionStatusEnum status; + private BigDecimal value; + +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/TransactionKafkaAdapter.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/TransactionKafkaAdapter.java new file mode 100644 index 0000000..cff982c --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/TransactionKafkaAdapter.java @@ -0,0 +1,32 @@ +package com.yape.antifraud.infra.out.adapter.kafka; + +import com.yape.antifraud.application.port.out.TransactionEdaRepository; +import com.yape.antifraud.config.exception.ErrorCode; +import com.yape.antifraud.config.properties.SpringConfigurationProperties; +import com.yape.antifraud.config.exception.KafkaException; +import com.yape.antifraud.infra.out.adapter.kafka.model.TransactionOutModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class TransactionKafkaAdapter implements TransactionEdaRepository { + private final KafkaTemplate kafkaTemplate; + private final SpringConfigurationProperties springConfigurationProperties; + public TransactionKafkaAdapter(KafkaTemplate kafkaTemplate, SpringConfigurationProperties springConfigurationProperties) { + this.kafkaTemplate = kafkaTemplate; + this.springConfigurationProperties = springConfigurationProperties; + } + @Override + public void updateTransaction(TransactionOutModel transactionModel) { + try { + log.info("sending to Transaction for update, transaction id:{}, status:{}", transactionModel.getId(), transactionModel.getStatus() ); + this.kafkaTemplate.send(springConfigurationProperties.getKafka().getTopic().getTransaction(), transactionModel); + this.kafkaTemplate.flush(); + } catch (Exception e) { + log.error("Occurred an error in kafka ", e); + throw new KafkaException(ErrorCode.KAFKA_EXCEPTION); + } + } +} diff --git a/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/model/TransactionOutModel.java b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/model/TransactionOutModel.java new file mode 100644 index 0000000..94076e9 --- /dev/null +++ b/microservices/antifraud-ms/src/main/java/com/yape/antifraud/infra/out/adapter/kafka/model/TransactionOutModel.java @@ -0,0 +1,20 @@ +package com.yape.antifraud.infra.out.adapter.kafka.model; + +import com.yape.antifraud.domain.TransactionStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.UUID; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TransactionOutModel implements Serializable { + private Long id; + private TransactionStatusEnum status; + private UUID code; +} diff --git a/microservices/antifraud-ms/src/main/resources/application.properties b/microservices/antifraud-ms/src/main/resources/application.properties new file mode 100644 index 0000000..86e4cf5 --- /dev/null +++ b/microservices/antifraud-ms/src/main/resources/application.properties @@ -0,0 +1,14 @@ +spring.application.name=antifraud-ms + +spring.kafka.bootstrap-servers=localhost:9092 +spring.kafka.topic.antifraud=topic-antifraud-review +spring.kafka.topic.transaction=topic-transaction-update +spring.kafka.producer.properties.spring.json.add.type.headers=false +spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer +spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer +spring.kafka.consumer.group-id=bcp-group +server.port=8081 + +#max value to review antifraud +antifraud.transaction.max.value=1000 + diff --git a/microservices/antifraud-ms/src/test/java/com/yape/antifraud/AntifraudMsApplicationTests.java b/microservices/antifraud-ms/src/test/java/com/yape/antifraud/AntifraudMsApplicationTests.java new file mode 100644 index 0000000..b4fb80e --- /dev/null +++ b/microservices/antifraud-ms/src/test/java/com/yape/antifraud/AntifraudMsApplicationTests.java @@ -0,0 +1,13 @@ +package com.yape.antifraud; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AntifraudMsApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/microservices/pom.xml b/microservices/pom.xml new file mode 100644 index 0000000..0c45195 --- /dev/null +++ b/microservices/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.0 + + + + com.yape + microservices + 1.0.0-SNAPSHOT + pom + microservices + Microservices for Spring Boot + + 17 + 17 + UTF-8 + + + + transaction-ms + antifraud-ms + transaction-bff + + + + + + + + + + + diff --git a/microservices/transaction-bff/.gitignore b/microservices/transaction-bff/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/microservices/transaction-bff/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices/transaction-bff/.mvn/wrapper/maven-wrapper.properties b/microservices/transaction-bff/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..aeccdfd --- /dev/null +++ b/microservices/transaction-bff/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.1 +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip diff --git a/microservices/transaction-bff/mvnw b/microservices/transaction-bff/mvnw new file mode 100644 index 0000000..ba9212a --- /dev/null +++ b/microservices/transaction-bff/mvnw @@ -0,0 +1,250 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.1 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl="${value-}" ;; + distributionSha256Sum) distributionSha256Sum="${value-}" ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/microservices/transaction-bff/mvnw.cmd b/microservices/transaction-bff/mvnw.cmd new file mode 100644 index 0000000..406932d --- /dev/null +++ b/microservices/transaction-bff/mvnw.cmd @@ -0,0 +1,146 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.1 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/microservices/transaction-bff/pom.xml b/microservices/transaction-bff/pom.xml new file mode 100644 index 0000000..5aacb67 --- /dev/null +++ b/microservices/transaction-bff/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + com.yape + microservices + 1.0.0-SNAPSHOT + + transaction-bff + 0.0.1-SNAPSHOT + transaction-bff + Transaction Bff project for Spring Boot + + 17 + 2023.0.1 + + + + org.springframework.boot + spring-boot-starter-graphql + + + com.graphql-java + graphql-java-extended-scalars + 20.0 + + + com.tailrocks.graphql + graphql-datetime-spring-boot-starter + 6.0.0 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-webflux + test + + + org.springframework.graphql + spring-graphql-test + test + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/TransactionBffApplication.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/TransactionBffApplication.java new file mode 100644 index 0000000..2859fae --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/TransactionBffApplication.java @@ -0,0 +1,16 @@ +package com.yape.transactionbff; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +@EnableFeignClients +public class TransactionBffApplication { + + public static void main(String[] args) { + SpringApplication.run(TransactionBffApplication.class, args); + } + +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/domain/Transaction.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/domain/Transaction.java new file mode 100644 index 0000000..3b4f3fe --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/domain/Transaction.java @@ -0,0 +1,22 @@ +package com.yape.transactionbff.application.domain; + +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionStatus; +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Builder +@Value +@AllArgsConstructor +public class Transaction { + UUID transactionExternalId; + TransactionType transactionType; + TransactionStatus transactionStatus; + BigDecimal value; + LocalDateTime createAt; +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/GetTransactionQuery.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/GetTransactionQuery.java new file mode 100644 index 0000000..8978809 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/GetTransactionQuery.java @@ -0,0 +1,11 @@ +package com.yape.transactionbff.application.port.in; + +import com.yape.transactionbff.application.domain.Transaction; + +import java.util.List; +import java.util.UUID; + +public interface GetTransactionQuery { + Transaction getTransactionByCode(UUID code); + List getAllTransactions(); +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/RegisterTransactionCommand.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/RegisterTransactionCommand.java new file mode 100644 index 0000000..794c501 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/in/RegisterTransactionCommand.java @@ -0,0 +1,21 @@ +package com.yape.transactionbff.application.port.in; + +import com.yape.transactionbff.application.domain.Transaction; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; +import java.util.UUID; + +public interface RegisterTransactionCommand { + Transaction execute(Command command); + @Value + @Builder + class Command { + UUID accountExternalIdDebit; + UUID accountExternalIdCredit; + Integer transferTypeId; + BigDecimal value; + } + +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/out/TransactionClient.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/out/TransactionClient.java new file mode 100644 index 0000000..9b1f673 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/port/out/TransactionClient.java @@ -0,0 +1,30 @@ +package com.yape.transactionbff.application.port.out; + + +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionModelRequest; +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionModelResponse; +import org.springframework.cloud.openfeign.FeignClient; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; +import java.util.UUID; + +@FeignClient( + name = "transactions", + url = "http://localhost:8080/transaction-ms" + +) +public interface TransactionClient { + @GetMapping(value = "/{code}",consumes = MediaType.APPLICATION_JSON_VALUE) + TransactionModelResponse getTransactionById(@PathVariable("code") UUID code); + + @GetMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + List getAllTransactions(); + + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + TransactionModelResponse register(@RequestBody TransactionModelRequest request); +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/GetTransactionUseCase.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/GetTransactionUseCase.java new file mode 100644 index 0000000..3cf14f4 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/GetTransactionUseCase.java @@ -0,0 +1,31 @@ +package com.yape.transactionbff.application.usecase; + +import com.yape.transactionbff.application.domain.Transaction; +import com.yape.transactionbff.application.port.in.GetTransactionQuery; +import com.yape.transactionbff.application.port.out.TransactionClient; +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionModelResponse; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class GetTransactionUseCase implements GetTransactionQuery { + private final TransactionClient transactionClient; + + public GetTransactionUseCase(TransactionClient transactionClient){ + this.transactionClient = transactionClient; + } + @Override + public Transaction getTransactionByCode(UUID code) { + return this.transactionClient.getTransactionById(code).toDomain(); + } + @Override + public List getAllTransactions() { + return this.transactionClient.getAllTransactions().stream() + .map(TransactionModelResponse::toDomain) + .collect(Collectors.toList()); + } + +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/RegisterTransactionUseCase.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/RegisterTransactionUseCase.java new file mode 100644 index 0000000..5368282 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/application/usecase/RegisterTransactionUseCase.java @@ -0,0 +1,30 @@ +package com.yape.transactionbff.application.usecase; + +import com.yape.transactionbff.application.domain.Transaction; +import com.yape.transactionbff.application.port.in.RegisterTransactionCommand; +import com.yape.transactionbff.application.port.out.TransactionClient; +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionModelRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class RegisterTransactionUseCase implements RegisterTransactionCommand { + private final TransactionClient transactionClient; + + public RegisterTransactionUseCase(TransactionClient transactionClient) { + this.transactionClient = transactionClient; + } + + @Override + public Transaction execute(Command command) { + TransactionModelRequest request = TransactionModelRequest.builder() + .value(command.getValue()) + .accountExternalIdDebit(command.getAccountExternalIdDebit()) + .accountExternalIdCredit(command.getAccountExternalIdCredit()) + .transferTypeId(command.getTransferTypeId()) + .build(); + + return transactionClient.register(request).toDomain(); + } +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/FeignConfig.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/FeignConfig.java new file mode 100644 index 0000000..5628021 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/FeignConfig.java @@ -0,0 +1,14 @@ +package com.yape.transactionbff.config; + +import feign.Logger; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FeignConfig { + @Bean + Logger.Level feignLoggerLevel(){ + return Logger.Level.FULL; + } + +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/GraphQlConfig.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/GraphQlConfig.java new file mode 100644 index 0000000..3223f62 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/GraphQlConfig.java @@ -0,0 +1,18 @@ +package com.yape.transactionbff.config; + +import graphql.scalars.ExtendedScalars; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; + +@Configuration +public class GraphQlConfig { + @Bean + public RuntimeWiringConfigurer runtimeWiringConfigurer() { + return wiringBuilder -> wiringBuilder + .scalar(ExtendedScalars.GraphQLBigDecimal) + .scalar(ExtendedScalars.UUID) + .scalar(ExtendedScalars.DateTime); + + } +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomErrorDecoder.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomErrorDecoder.java new file mode 100644 index 0000000..05bf811 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomErrorDecoder.java @@ -0,0 +1,18 @@ +package com.yape.transactionbff.config.exception; + +import feign.Response; +import feign.codec.ErrorDecoder; +import org.springframework.stereotype.Component; + +@Component +public class CustomErrorDecoder implements ErrorDecoder { + private final ErrorDecoder errorDecoder = new Default(); + + @Override + public Exception decode(String s, Response response) { + if (response.status() == 404) { + return new NotFoundException("Transaction not found"); + } + return new Exception("Unexpected exception"); + } +} \ No newline at end of file diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomExceptionResolver.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomExceptionResolver.java new file mode 100644 index 0000000..3a46347 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/CustomExceptionResolver.java @@ -0,0 +1,22 @@ +package com.yape.transactionbff.config.exception; + +import graphql.GraphQLError; +import graphql.GraphqlErrorBuilder; +import graphql.schema.DataFetchingEnvironment; +import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter; +import org.springframework.graphql.execution.ErrorType; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +@Component +public class CustomExceptionResolver extends DataFetcherExceptionResolverAdapter { + @Override + protected GraphQLError resolveToSingleError(@NonNull Throwable ex, @NonNull DataFetchingEnvironment env){ + return GraphqlErrorBuilder.newError() + .errorType(ErrorType.BAD_REQUEST) + .message(ex.getMessage() != null ? ex.getMessage() : ex.getCause().getMessage() ) + .path(env.getExecutionStepInfo().getPath()) + .location(env.getField().getSourceLocation()) + .build(); + } +} \ No newline at end of file diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorHandler.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorHandler.java new file mode 100644 index 0000000..a2fe3af --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorHandler.java @@ -0,0 +1,44 @@ +package com.yape.transactionbff.config.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(Throwable.class) + public ResponseEntity handle(Throwable ex) { + log.error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), ex); + ErrorResponse response = ErrorResponse.builder() + .code(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .description(ex.getMessage()).build(); + + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handle(Exception ex) { + log.error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), ex); + ErrorResponse response = ErrorResponse.builder() + .code(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .description(ex.getMessage()).build(); + + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handle(NotFoundException ex) { + log.error(HttpStatus.NOT_FOUND.getReasonPhrase(), ex); + ErrorResponse response = ErrorResponse.builder() + .code(HttpStatus.NOT_FOUND.value()) + .description(ex.getMessage()).build(); + + return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); + } + } + + diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorResponse.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorResponse.java new file mode 100644 index 0000000..f052cc3 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/ErrorResponse.java @@ -0,0 +1,12 @@ +package com.yape.transactionbff.config.exception; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; + +@Builder +public class ErrorResponse { + @JsonProperty + Integer code; + @JsonProperty + String description; +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/NotFoundException.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/NotFoundException.java new file mode 100644 index 0000000..972599c --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/config/exception/NotFoundException.java @@ -0,0 +1,8 @@ +package com.yape.transactionbff.config.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } + +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/TransactionControllerGraph.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/TransactionControllerGraph.java new file mode 100644 index 0000000..8ba6dbf --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/TransactionControllerGraph.java @@ -0,0 +1,51 @@ +package com.yape.transactionbff.infra.in.adapter.graphql; + +import com.yape.transactionbff.application.port.in.GetTransactionQuery; +import com.yape.transactionbff.application.port.in.RegisterTransactionCommand; +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionCreateModelResponse; +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionModelRequest; +import com.yape.transactionbff.infra.in.adapter.graphql.model.TransactionModelResponse; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Controller +public class TransactionControllerGraph { + private final GetTransactionQuery getTransactionQuery; + private final RegisterTransactionCommand registerTransactionCommand; + + public TransactionControllerGraph(GetTransactionQuery getTransactionQuery, RegisterTransactionCommand registerTransactionCommand) { + this.getTransactionQuery = getTransactionQuery; + this.registerTransactionCommand = registerTransactionCommand; + } + + @QueryMapping + public TransactionModelResponse getTransactionByCode(@Argument UUID code) { + return TransactionModelResponse.fromDomain(this.getTransactionQuery.getTransactionByCode(code)); + } + + @QueryMapping + public List getAllTransactions() { + return this.getTransactionQuery.getAllTransactions().stream() + .map(TransactionModelResponse::fromDomain) + .collect(Collectors.toList()); + } + + @MutationMapping + public TransactionCreateModelResponse register(@Argument TransactionModelRequest request) { + RegisterTransactionCommand.Command command = RegisterTransactionCommand.Command.builder() + .accountExternalIdCredit(request.getAccountExternalIdCredit()) + .accountExternalIdDebit(request.getAccountExternalIdDebit()) + .transferTypeId(request.getTransferTypeId()) + .value(request.getValue()) + .build(); + + return TransactionCreateModelResponse.fromDomain(this.registerTransactionCommand.execute(command)); + } + +} \ No newline at end of file diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionCreateModelResponse.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionCreateModelResponse.java new file mode 100644 index 0000000..b488ae5 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionCreateModelResponse.java @@ -0,0 +1,40 @@ +package com.yape.transactionbff.infra.in.adapter.graphql.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.yape.transactionbff.application.domain.Transaction; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TransactionCreateModelResponse { + private UUID transactionExternalId; + private TransactionType transactionType; + private BigDecimal value; + private LocalDateTime createAt; + + public Transaction toDomain() { + return Transaction.builder() + .transactionExternalId(transactionExternalId) + .transactionType(transactionType) + .value(value) + .createAt(createAt) + .build(); + } + + public static TransactionCreateModelResponse fromDomain(Transaction transaction) { + return TransactionCreateModelResponse.builder() + .transactionExternalId(transaction.getTransactionExternalId()) + .transactionType(transaction.getTransactionType()) + .value(transaction.getValue()) + .createAt(transaction.getCreateAt()) + .build(); + } +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelRequest.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelRequest.java new file mode 100644 index 0000000..34251f1 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelRequest.java @@ -0,0 +1,20 @@ +package com.yape.transactionbff.infra.in.adapter.graphql.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.UUID; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TransactionModelRequest { + private UUID accountExternalIdDebit; + private UUID accountExternalIdCredit; + private Integer transferTypeId; + private BigDecimal value; +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelResponse.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelResponse.java new file mode 100644 index 0000000..1ea88e4 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionModelResponse.java @@ -0,0 +1,44 @@ +package com.yape.transactionbff.infra.in.adapter.graphql.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.yape.transactionbff.application.domain.Transaction; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TransactionModelResponse { + private UUID transactionExternalId; + private TransactionType transactionType; + private TransactionStatus transactionStatus; + private BigDecimal value; + private LocalDateTime createAt; + + public Transaction toDomain() { + return Transaction.builder() + .transactionExternalId(transactionExternalId) + .transactionType(transactionType) + .transactionStatus(transactionStatus) + .value(value) + .createAt(createAt) + .build(); + } + + public static TransactionModelResponse fromDomain(Transaction transaction) { + return TransactionModelResponse.builder() + .transactionExternalId(transaction.getTransactionExternalId()) + .transactionType(transaction.getTransactionType()) + .transactionStatus(transaction.getTransactionStatus()) + .value(transaction.getValue()) + .createAt(transaction.getCreateAt()) + .build(); + } + +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionStatus.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionStatus.java new file mode 100644 index 0000000..8f03496 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionStatus.java @@ -0,0 +1,18 @@ +package com.yape.transactionbff.infra.in.adapter.graphql.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@NoArgsConstructor +public class TransactionStatus { + private String name; +} diff --git a/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionType.java b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionType.java new file mode 100644 index 0000000..e54edf7 --- /dev/null +++ b/microservices/transaction-bff/src/main/java/com/yape/transactionbff/infra/in/adapter/graphql/model/TransactionType.java @@ -0,0 +1,18 @@ +package com.yape.transactionbff.infra.in.adapter.graphql.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@NoArgsConstructor +public class TransactionType { + private String name; +} diff --git a/microservices/transaction-bff/src/main/resources/application.properties b/microservices/transaction-bff/src/main/resources/application.properties new file mode 100644 index 0000000..1d34aa4 --- /dev/null +++ b/microservices/transaction-bff/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.application.name=transaction-bff +spring.graphql.graphiql.enabled=true +spring.graphql.graphiql.path=/graphiql +spring.mvc.favicon.enabled=false +server.port=8090 diff --git a/microservices/transaction-bff/src/main/resources/graphql/schema.graphqls b/microservices/transaction-bff/src/main/resources/graphql/schema.graphqls new file mode 100644 index 0000000..dc9ef58 --- /dev/null +++ b/microservices/transaction-bff/src/main/resources/graphql/schema.graphqls @@ -0,0 +1,43 @@ +scalar BigDecimal +scalar LocalDateTime +scalar UUID + +type TransactionModelResponse { + transactionExternalId: UUID! + transactionType: TransactionType + transactionStatus: TransactionStatus + value: BigDecimal + createAt: LocalDateTime +} + +type TransactionCreateModelResponse { + transactionExternalId: UUID! + transactionType: TransactionType + value: BigDecimal + createAt: LocalDateTime +} + +type TransactionType { + name : String +} + +type TransactionStatus { + name : String +} + +input TransactionModelRequest { + accountExternalIdDebit : UUID + accountExternalIdCredit: UUID + transferTypeId: Int + value: BigDecimal +} + +type Query { + getTransactionByCode(code : UUID) : TransactionModelResponse + getAllTransactions : [TransactionModelResponse] +} + +type Mutation { + register(request : TransactionModelRequest) : TransactionCreateModelResponse +} + diff --git a/microservices/transaction-bff/src/main/resources/static/favicon.ico b/microservices/transaction-bff/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3cb65d9c65d289d844d725bee6978ddd30b59418 GIT binary patch literal 14632 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

O^&o59{7ZlV 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 SE5-o{P<*E*SN+!X%l`*DeT>rp literal 0 HcmV?d00001 diff --git a/microservices/transaction-bff/src/test/java/com/yape/transactionbff/TransactionBffApplicationTests.java b/microservices/transaction-bff/src/test/java/com/yape/transactionbff/TransactionBffApplicationTests.java new file mode 100644 index 0000000..5924f70 --- /dev/null +++ b/microservices/transaction-bff/src/test/java/com/yape/transactionbff/TransactionBffApplicationTests.java @@ -0,0 +1,13 @@ +package com.yape.transactionbff; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TransactionBffApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/microservices/transaction-ms/.gitignore b/microservices/transaction-ms/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/microservices/transaction-ms/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices/transaction-ms/.mvn/wrapper/maven-wrapper.properties b/microservices/transaction-ms/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..aeccdfd --- /dev/null +++ b/microservices/transaction-ms/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.1 +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip diff --git a/microservices/transaction-ms/mvnw b/microservices/transaction-ms/mvnw new file mode 100644 index 0000000..ba9212a --- /dev/null +++ b/microservices/transaction-ms/mvnw @@ -0,0 +1,250 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.1 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl="${value-}" ;; + distributionSha256Sum) distributionSha256Sum="${value-}" ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/microservices/transaction-ms/mvnw.cmd b/microservices/transaction-ms/mvnw.cmd new file mode 100644 index 0000000..406932d --- /dev/null +++ b/microservices/transaction-ms/mvnw.cmd @@ -0,0 +1,146 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.1 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/microservices/transaction-ms/pom.xml b/microservices/transaction-ms/pom.xml new file mode 100644 index 0000000..0ee67cb --- /dev/null +++ b/microservices/transaction-ms/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + com.yape + microservices + 1.0.0-SNAPSHOT + + transaction-ms + 0.0.1-SNAPSHOT + transaction-ms + Transaction-ms for Spring Boot + + 17 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.kafka + spring-kafka + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-cache + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.kafka + spring-kafka-test + test + + + org.springframework + spring-context + 6.1.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/TransactionMsApplication.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/TransactionMsApplication.java new file mode 100644 index 0000000..77fd30e --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/TransactionMsApplication.java @@ -0,0 +1,13 @@ +package com.yape.transaction; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TransactionMsApplication { + + public static void main(String[] args) { + SpringApplication.run(TransactionMsApplication.class, args); + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/exception/NotFoundException.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/exception/NotFoundException.java new file mode 100644 index 0000000..2b46714 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package com.yape.transaction.application.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String msg) { + super(msg); + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/GetTransactionQuery.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/GetTransactionQuery.java new file mode 100644 index 0000000..4ee1d01 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/GetTransactionQuery.java @@ -0,0 +1,12 @@ +package com.yape.transaction.application.port.in; + +import com.yape.transaction.domain.Transaction; + +import java.util.List; +import java.util.UUID; + +public interface GetTransactionQuery { + Transaction getTransactionByCode(UUID code); + + List getAllTransactions(); +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/RegisterTransactionCommand.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/RegisterTransactionCommand.java new file mode 100644 index 0000000..d735b9f --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/RegisterTransactionCommand.java @@ -0,0 +1,21 @@ +package com.yape.transaction.application.port.in; + +import com.yape.transaction.domain.Transaction; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; +import java.util.UUID; + +public interface RegisterTransactionCommand { + Transaction execute(Command command); + @Value + @Builder + class Command { + UUID accountExternalIdDebit; + UUID accountExternalIdCredit; + Integer transferTypeId; + BigDecimal value; + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionCommand.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionCommand.java new file mode 100644 index 0000000..b9bf127 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/in/UpdateTransactionCommand.java @@ -0,0 +1,19 @@ +package com.yape.transaction.application.port.in; + +import com.yape.transaction.domain.type.TransactionStatusEnum; +import lombok.Builder; +import lombok.Value; + +import java.util.UUID; + +public interface UpdateTransactionCommand { + void execute(Command command); + @Value + @Builder + class Command { + Long id; + UUID code; + TransactionStatusEnum status; + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/AntiFraudEdaRepository.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/AntiFraudEdaRepository.java new file mode 100644 index 0000000..c97fccd --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/AntiFraudEdaRepository.java @@ -0,0 +1,7 @@ +package com.yape.transaction.application.port.out; + +import com.yape.transaction.domain.Transaction; + +public interface AntiFraudEdaRepository { + void validate(Transaction transaction); +} \ No newline at end of file diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/CacheRepository.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/CacheRepository.java new file mode 100644 index 0000000..8824555 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/CacheRepository.java @@ -0,0 +1,12 @@ +package com.yape.transaction.application.port.out; + +import com.yape.transaction.domain.Transaction; + + +public interface CacheRepository { + Transaction findTransactionById(Long id); + void removeTransactionById(Long id); + Transaction saveTransaction(Transaction transaction); +} + + diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/TransactionRepository.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/TransactionRepository.java new file mode 100644 index 0000000..8bad1ee --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/port/out/TransactionRepository.java @@ -0,0 +1,25 @@ +package com.yape.transaction.application.port.out; + +import com.yape.transaction.infra.out.adapter.data.model.TransactionDataModel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface TransactionRepository extends JpaRepository { + Optional findOptionalByCode(UUID code); + Optional findOptionalById(Long id); + + @Modifying + @Query("update transaction t set t.status = :status, t.updateAt = :updateAt where t.id = :id") + void updateStatus(@Param(value = "id") Long id, @Param(value = "status") String status, @Param(value = "updateAt") LocalDateTime updateAt); +} + + diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/GetTransactionUseCase.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/GetTransactionUseCase.java new file mode 100644 index 0000000..ca8daa2 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/GetTransactionUseCase.java @@ -0,0 +1,34 @@ +package com.yape.transaction.application.usecase; + +import com.yape.transaction.application.exception.NotFoundException; +import com.yape.transaction.application.port.in.GetTransactionQuery; +import com.yape.transaction.application.port.out.TransactionRepository; +import com.yape.transaction.domain.Transaction; +import com.yape.transaction.infra.out.adapter.data.model.TransactionDataModel; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class GetTransactionUseCase implements GetTransactionQuery { + private final TransactionRepository repository; + + public GetTransactionUseCase(TransactionRepository repository){ + this.repository = repository; + } + @Override + public Transaction getTransactionByCode(UUID code) { + return this.repository.findOptionalByCode(code) + .orElseThrow(() -> new NotFoundException("Transaction not found")) + .toDomain(); + } + @Override + public List getAllTransactions() { + return this.repository.findAll().stream() + .map(TransactionDataModel::toDomain) + .collect(Collectors.toList()); + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/RegisterTransactionUseCase.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/RegisterTransactionUseCase.java new file mode 100644 index 0000000..1eb9c47 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/RegisterTransactionUseCase.java @@ -0,0 +1,49 @@ +package com.yape.transaction.application.usecase; + +import com.yape.transaction.application.port.in.RegisterTransactionCommand; +import com.yape.transaction.application.port.out.AntiFraudEdaRepository; +import com.yape.transaction.application.port.out.CacheRepository; +import com.yape.transaction.application.port.out.TransactionRepository; +import com.yape.transaction.domain.Transaction; +import com.yape.transaction.domain.type.TransactionStatusEnum; +import com.yape.transaction.domain.type.TransactionTypeEnum; +import com.yape.transaction.domain.type.TransferTypeEnum; +import com.yape.transaction.infra.out.adapter.data.model.TransactionDataModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Service +@Slf4j +public class RegisterTransactionUseCase implements RegisterTransactionCommand { + private final TransactionRepository repository; + private final AntiFraudEdaRepository antiFraudEdaRepository; + private final CacheRepository cacheRepository; + + public RegisterTransactionUseCase(TransactionRepository repository, AntiFraudEdaRepository antiFraudEdaRepository, CacheRepository cacheRepository) { + this.repository = repository; + this.antiFraudEdaRepository = antiFraudEdaRepository; + this.cacheRepository = cacheRepository; + } + + @Override + public Transaction execute(Command command) { + TransactionDataModel entity = TransactionDataModel.builder() + .code(UUID.randomUUID()) + .accountExternalIdCredit(command.getAccountExternalIdCredit()) + .accountExternalIdDebit(command.getAccountExternalIdDebit()) + .value(command.getValue()) + .transferType(TransferTypeEnum.findByValue(command.getTransferTypeId()).name()) + .status(TransactionStatusEnum.PENDING.name()) + .type( TransactionTypeEnum.TRANSFER.name()) + .createAt(LocalDateTime.now()) + .build(); + Transaction transaction = this.repository.save(entity).toDomain(); + this.cacheRepository.saveTransaction(transaction); + this.antiFraudEdaRepository.validate(transaction); + + return transaction; + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/UpdateTransactionUseCase.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/UpdateTransactionUseCase.java new file mode 100644 index 0000000..98ffa45 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/application/usecase/UpdateTransactionUseCase.java @@ -0,0 +1,34 @@ +package com.yape.transaction.application.usecase; + +import com.yape.transaction.application.port.in.UpdateTransactionCommand; +import com.yape.transaction.application.port.out.CacheRepository; +import com.yape.transaction.application.port.out.TransactionRepository; +import com.yape.transaction.domain.Transaction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@Slf4j +public class UpdateTransactionUseCase implements UpdateTransactionCommand { + private final TransactionRepository repository; + private final CacheRepository cacheRepository; + + public UpdateTransactionUseCase(TransactionRepository repository, CacheRepository cacheRepository) { + this.repository = repository; + this.cacheRepository = cacheRepository; + } + + @Transactional + @Override + public void execute(Command command) { + Transaction transaction = this.cacheRepository.findTransactionById(command.getId()); + this.repository.updateStatus(transaction.getId(), command.getStatus().name(), LocalDateTime.now()); + log.info("updated id:{}", transaction.getId()); + + this.cacheRepository.removeTransactionById(transaction.getId()); + } + +} \ No newline at end of file diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/CacheConfig.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/CacheConfig.java new file mode 100644 index 0000000..b792bfd --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/CacheConfig.java @@ -0,0 +1,10 @@ +package com.yape.transaction.config; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +public class CacheConfig { + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/KafkaConfig.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/KafkaConfig.java new file mode 100644 index 0000000..6933150 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/KafkaConfig.java @@ -0,0 +1,53 @@ +package com.yape.transaction.config; + +import com.yape.transaction.config.properties.SpringConfigurationProperties; +import com.yape.transaction.infra.in.adapter.kafka.model.TransactionInModel; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer; +import org.springframework.kafka.support.serializer.JsonDeserializer; + +import java.util.HashMap; +import java.util.Map; + +@EnableKafka +@Configuration +@Slf4j +public class KafkaConfig { + + private final SpringConfigurationProperties springConfigurationProperties; + private static final String EARLIEST = "earliest"; + public KafkaConfig(SpringConfigurationProperties springConfigurationProperties) { + this.springConfigurationProperties = springConfigurationProperties; + } + + @Bean + public ConsumerFactory transactionConsumerConfigs() { + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, springConfigurationProperties.getKafka().getBootstrapServers()); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class); + props.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class); + props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, StringDeserializer.class); + + props.put(ConsumerConfig.GROUP_ID_CONFIG, springConfigurationProperties.getKafka().getConsumer().getGroupId()); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, EARLIEST); + return new DefaultKafkaConsumerFactory<>(props, new ErrorHandlingDeserializer<>(), new ErrorHandlingDeserializer<>(new JsonDeserializer<>(TransactionInModel.class))); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory transactionKafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory + factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(transactionConsumerConfigs()); + return factory; + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorCode.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorCode.java new file mode 100644 index 0000000..76ed6f8 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorCode.java @@ -0,0 +1,26 @@ +package com.yape.transaction.config.exception; + +import lombok.Getter; + +public enum ErrorCode { + KAFKA_EXCEPTION(109, "Kafka Error", "KAFKA_EXCEPTION"), + INTERNAL_ERROR(108,"Internal Error","INTERNAL_ERROR"), + TRANSACTION_NOT_FOUND(110,"Transaction Not Found","NOT_FOUND"); + + private final int value; + @Getter + private final String reasonPhrase; + @Getter + private final String code; + + ErrorCode(int value, String reasonPhrase, String code) { + this.value = value; + this.reasonPhrase = reasonPhrase; + this.code = code; + } + + public int value() { + return this.value; + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorHandler.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorHandler.java new file mode 100644 index 0000000..15b0145 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorHandler.java @@ -0,0 +1,47 @@ +package com.yape.transaction.config.exception; + +import com.yape.transaction.application.exception.NotFoundException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@Slf4j +@ControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(Throwable.class) + public ResponseEntity handle(Throwable ex) { + log.error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), ex); + return buildResponseError(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.INTERNAL_ERROR); + } + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handle(NotFoundException ex) { + log.error(ErrorCode.TRANSACTION_NOT_FOUND.getCode(), ex); + return buildResponseError(HttpStatus.NOT_FOUND, ErrorCode.TRANSACTION_NOT_FOUND); + } + + @ExceptionHandler(KafkaException.class) + public ResponseEntity handle(KafkaException ex) { + log.error(ex.getCode().getCode(), ex); + return buildResponseError(HttpStatus.SERVICE_UNAVAILABLE, ex.getCode()); + } + + private ResponseEntity buildCustomResponseError(HttpStatus httpStatus, ErrorCode errorCode, String customDescription) { + final var response = ErrorResponse.builder() + .errorInternalCode(errorCode.value()) + .errorDescription(customDescription) + .errorCode(errorCode.getCode()) + .build(); + + return new ResponseEntity<>(response, httpStatus); + } + + private ResponseEntity buildResponseError(HttpStatus httpStatus, ErrorCode errorCode) { + return buildCustomResponseError(httpStatus, errorCode, errorCode.getReasonPhrase()); + } + +} + diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorResponse.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorResponse.java new file mode 100644 index 0000000..1e4ea8f --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/ErrorResponse.java @@ -0,0 +1,15 @@ +package com.yape.transaction.config.exception; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; + +@Builder +public class ErrorResponse { + @JsonProperty + int errorInternalCode; + @JsonProperty + String errorDescription; + @JsonProperty + String errorCode; +} + diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/GenericException.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/GenericException.java new file mode 100644 index 0000000..1d30a16 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/GenericException.java @@ -0,0 +1,15 @@ +package com.yape.transaction.config.exception; + +public abstract class GenericException extends RuntimeException { + private final ErrorCode errorCode; + + public GenericException(ErrorCode errorCode) { + super(errorCode.getReasonPhrase()); + this.errorCode = errorCode; + } + + public ErrorCode getCode() { + return this.errorCode; + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/KafkaException.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/KafkaException.java new file mode 100644 index 0000000..dcf78ab --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/exception/KafkaException.java @@ -0,0 +1,7 @@ +package com.yape.transaction.config.exception; + +public class KafkaException extends GenericException { + public KafkaException(ErrorCode ec){ + super(ec); + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/ConsumerKafkaProperties.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/ConsumerKafkaProperties.java new file mode 100644 index 0000000..a763d0f --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/ConsumerKafkaProperties.java @@ -0,0 +1,8 @@ +package com.yape.transaction.config.properties; + +import lombok.Data; + +@Data +public class ConsumerKafkaProperties { + private String groupId; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/KafkaProperties.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/KafkaProperties.java new file mode 100644 index 0000000..d57e749 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/KafkaProperties.java @@ -0,0 +1,13 @@ +package com.yape.transaction.config.properties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@AllArgsConstructor +@NoArgsConstructor +public class KafkaProperties { + private String bootstrapServers; + private TopicProperties topic; + private ConsumerKafkaProperties consumer; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/SpringConfigurationProperties.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/SpringConfigurationProperties.java new file mode 100644 index 0000000..95f688d --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/SpringConfigurationProperties.java @@ -0,0 +1,16 @@ +package com.yape.transaction.config.properties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "spring") +@AllArgsConstructor +@NoArgsConstructor +@Data +public class SpringConfigurationProperties { + private KafkaProperties kafka; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/TopicProperties.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/TopicProperties.java new file mode 100644 index 0000000..7fe06f9 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/config/properties/TopicProperties.java @@ -0,0 +1,12 @@ +package com.yape.transaction.config.properties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +@Data +@AllArgsConstructor +@NoArgsConstructor +public class TopicProperties { + private String antifraud; + private String transaction; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/Transaction.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/Transaction.java new file mode 100644 index 0000000..62e91e8 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/Transaction.java @@ -0,0 +1,29 @@ +package com.yape.transaction.domain; + +import com.yape.transaction.domain.type.TransactionStatusEnum; +import com.yape.transaction.domain.type.TransactionTypeEnum; +import com.yape.transaction.domain.type.TransferTypeEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Builder +@Value +@AllArgsConstructor +public class Transaction { + Long id; + UUID code; + UUID accountExternalIdDebit; + UUID accountExternalIdCredit; + TransferTypeEnum transferType; + TransactionTypeEnum type; + TransactionStatusEnum status; + + BigDecimal value; + LocalDateTime createAt; + LocalDateTime updateAt; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionStatusEnum.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionStatusEnum.java new file mode 100644 index 0000000..5508a39 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionStatusEnum.java @@ -0,0 +1,7 @@ +package com.yape.transaction.domain.type; + +public enum TransactionStatusEnum { + PENDING, + APPROVED, + REJECTED +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionTypeEnum.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionTypeEnum.java new file mode 100644 index 0000000..6a62f5c --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransactionTypeEnum.java @@ -0,0 +1,7 @@ +package com.yape.transaction.domain.type; + +public enum TransactionTypeEnum { + DEPOSIT, + WITHDRAW, + TRANSFER +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransferTypeEnum.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransferTypeEnum.java new file mode 100644 index 0000000..fab5cae --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/domain/type/TransferTypeEnum.java @@ -0,0 +1,18 @@ +package com.yape.transaction.domain.type; + +import java.util.Arrays; + +public enum TransferTypeEnum { + ONUS(1), + INTERBANK(2); + private final int value; + TransferTypeEnum(int value){ + this.value = value; + } + public static TransferTypeEnum findByValue(int value) { + return Arrays.stream(TransferTypeEnum.values()) + .filter(e -> e.value == value) + .findAny() + .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", value))); + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/TransactionListenerAdapter.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/TransactionListenerAdapter.java new file mode 100644 index 0000000..524e1b1 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/TransactionListenerAdapter.java @@ -0,0 +1,40 @@ +package com.yape.transaction.infra.in.adapter.kafka; + +import com.yape.transaction.application.port.in.UpdateTransactionCommand; +import com.yape.transaction.config.exception.ErrorCode; +import com.yape.transaction.infra.in.adapter.kafka.model.TransactionInModel; +import com.yape.transaction.config.exception.KafkaException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class TransactionListenerAdapter { + private final UpdateTransactionCommand updateTransactionCommand; + + public TransactionListenerAdapter(UpdateTransactionCommand updateTransactionCommand) { + this.updateTransactionCommand = updateTransactionCommand; + } + + @KafkaListener( + topics = "${spring.kafka.topic.transaction}", + groupId = "${spring.kafka.consumer.group-id}", + containerFactory = "transactionKafkaListenerContainerFactory" + + ) + public void listen(TransactionInModel message) { + log.info("TransactionListenerAdapter message received from kafka {}", message); + try { + UpdateTransactionCommand.Command command = UpdateTransactionCommand.Command.builder() + .code(message.getCode()) + .id(message.getId()) + .status(message.getStatus()) + .build(); + this.updateTransactionCommand.execute(command); + } catch (Exception ex) { + log.error("Occurred an error in kafka ", ex); + throw new KafkaException(ErrorCode.KAFKA_EXCEPTION); + } + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/model/TransactionInModel.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/model/TransactionInModel.java new file mode 100644 index 0000000..2353be5 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/kafka/model/TransactionInModel.java @@ -0,0 +1,21 @@ +package com.yape.transaction.infra.in.adapter.kafka.model; + +import com.yape.transaction.domain.type.TransactionStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.UUID; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class TransactionInModel implements Serializable { + private Long id; + private UUID code; + private TransactionStatusEnum status; + +} \ No newline at end of file diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/TransactionControllerAdapter.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/TransactionControllerAdapter.java new file mode 100644 index 0000000..52ec2d3 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/TransactionControllerAdapter.java @@ -0,0 +1,58 @@ +package com.yape.transaction.infra.in.adapter.rest; + +import com.yape.transaction.application.port.in.GetTransactionQuery; +import com.yape.transaction.application.port.in.RegisterTransactionCommand; +import com.yape.transaction.infra.in.adapter.rest.model.TransactionModelRequest; +import com.yape.transaction.infra.in.adapter.rest.model.TransactionModelResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/transaction-ms") +public class TransactionControllerAdapter { + + private final GetTransactionQuery getTransactionQuery; + private final RegisterTransactionCommand registerTransactionCommand; + + public TransactionControllerAdapter(GetTransactionQuery getTransactionQuery, + RegisterTransactionCommand registerTransactionCommand){ + this.getTransactionQuery = getTransactionQuery; + this.registerTransactionCommand = registerTransactionCommand; + } + @GetMapping("/{code}") + public ResponseEntity getTransactionByCode(@PathVariable UUID code) { + TransactionModelResponse response = TransactionModelResponse.fromDomain(this.getTransactionQuery.getTransactionByCode(code)); + return ResponseEntity.ok(response); + } + + @GetMapping + public ResponseEntity> getAllTransactions() { + List response = this.getTransactionQuery.getAllTransactions().stream() + .map(TransactionModelResponse::fromDomain) + .collect(Collectors.toList()); + return ResponseEntity.ok(response); + } + + @PostMapping + public ResponseEntity register(@RequestBody TransactionModelRequest request) { + RegisterTransactionCommand.Command command = RegisterTransactionCommand.Command.builder() + .accountExternalIdCredit(request.getAccountExternalIdCredit()) + .accountExternalIdDebit(request.getAccountExternalIdDebit()) + .transferTypeId(request.getTransferTypeId()) + .value(request.getValue()) + .build(); + TransactionModelResponse response = TransactionModelResponse.fromDomain(this.registerTransactionCommand.execute(command)); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelRequest.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelRequest.java new file mode 100644 index 0000000..2c57b20 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelRequest.java @@ -0,0 +1,20 @@ +package com.yape.transaction.infra.in.adapter.rest.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.UUID; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TransactionModelRequest { + private UUID accountExternalIdDebit; + private UUID accountExternalIdCredit; + private Integer transferTypeId; + private BigDecimal value; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelResponse.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelResponse.java new file mode 100644 index 0000000..8583034 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionModelResponse.java @@ -0,0 +1,34 @@ +package com.yape.transaction.infra.in.adapter.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.yape.transaction.domain.Transaction; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TransactionModelResponse { + private UUID transactionExternalId; + private TransactionType transactionType; + private TransactionStatus transactionStatus; + private BigDecimal value; + private LocalDateTime createAt; + + public static TransactionModelResponse fromDomain(Transaction transaction) { + return TransactionModelResponse.builder() + .transactionExternalId(transaction.getCode()) + .transactionType(TransactionType.builder().name(transaction.getType().name()).build()) + .transactionStatus(TransactionStatus.builder().name(transaction.getStatus().name()).build()) + .value(transaction.getValue()) + .createAt(transaction.getCreateAt()) + .build(); + } + +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionStatus.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionStatus.java new file mode 100644 index 0000000..b1a8365 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionStatus.java @@ -0,0 +1,17 @@ +package com.yape.transaction.infra.in.adapter.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class TransactionStatus { + private String name; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionType.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionType.java new file mode 100644 index 0000000..32f13e4 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/in/adapter/rest/model/TransactionType.java @@ -0,0 +1,17 @@ +package com.yape.transaction.infra.in.adapter.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class TransactionType { + private String name; +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/cache/CacheRepositoryAdapter.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/cache/CacheRepositoryAdapter.java new file mode 100644 index 0000000..29d03cf --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/cache/CacheRepositoryAdapter.java @@ -0,0 +1,39 @@ +package com.yape.transaction.infra.out.adapter.cache; + +import com.yape.transaction.application.exception.NotFoundException; +import com.yape.transaction.application.port.out.CacheRepository; +import com.yape.transaction.application.port.out.TransactionRepository; +import com.yape.transaction.domain.Transaction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class CacheRepositoryAdapter implements CacheRepository { + private final TransactionRepository repository; + public CacheRepositoryAdapter(TransactionRepository repository){ + this.repository = repository; + } + @Cacheable(cacheNames= "transactionCache", key="#id") + @Override + public Transaction findTransactionById(Long id){ + return this.repository.findOptionalById(id) + .orElseThrow(() -> new NotFoundException("transaction not found")).toDomain(); + } + + @CacheEvict(cacheNames = "transactionCache", key = "#id") + @Override + public void removeTransactionById(Long id) { + log.info("deleting from cache id:{}", id); + } + + @CachePut(cacheNames="transactionCache", key="#transaction.id") + @Override + public Transaction saveTransaction(Transaction transaction) { + log.info("saving cache id:{}", transaction.getId()); + return transaction; + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/data/model/TransactionDataModel.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/data/model/TransactionDataModel.java new file mode 100644 index 0000000..7376311 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/data/model/TransactionDataModel.java @@ -0,0 +1,60 @@ +package com.yape.transaction.infra.out.adapter.data.model; + +import com.yape.transaction.domain.Transaction; +import com.yape.transaction.domain.type.TransactionStatusEnum; +import com.yape.transaction.domain.type.TransactionTypeEnum; +import com.yape.transaction.domain.type.TransferTypeEnum; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Builder +@Entity(name = "transaction") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TransactionDataModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name ="code") + private UUID code; + @Column(name = "account_external_id_debit") + private UUID accountExternalIdDebit; + @Column(name = "account_external_id_credit") + private UUID accountExternalIdCredit; + private String transferType; + private String type; + private String status; + private BigDecimal value; + @Column(name = "created_at") + private LocalDateTime createAt; + @Column(name = "updated_at") + private LocalDateTime updateAt; + + public Transaction toDomain() { + return Transaction.builder() + .id(id) + .code(code) + .accountExternalIdDebit(accountExternalIdDebit) + .accountExternalIdCredit(accountExternalIdCredit) + .transferType(TransferTypeEnum.valueOf(transferType)) + .type(TransactionTypeEnum.valueOf(type)) + .status(TransactionStatusEnum.valueOf(status)) + .value(value) + .createAt(createAt) + .updateAt(updateAt) + .build(); + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/AntiFraudKafkaAdapter.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/AntiFraudKafkaAdapter.java new file mode 100644 index 0000000..35f3c28 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/AntiFraudKafkaAdapter.java @@ -0,0 +1,37 @@ +package com.yape.transaction.infra.out.adapter.kafka; + +import com.yape.transaction.application.port.out.AntiFraudEdaRepository; +import com.yape.transaction.config.exception.ErrorCode; +import com.yape.transaction.config.properties.SpringConfigurationProperties; +import com.yape.transaction.domain.Transaction; +import com.yape.transaction.config.exception.KafkaException; +import com.yape.transaction.infra.out.adapter.kafka.model.TransactionOutModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class AntiFraudKafkaAdapter implements AntiFraudEdaRepository { + private final KafkaTemplate kafkaTemplate; + private final SpringConfigurationProperties springConfigurationProperties; + + public AntiFraudKafkaAdapter(KafkaTemplate kafkaTemplate, SpringConfigurationProperties springConfigurationProperties) { + this.kafkaTemplate = kafkaTemplate; + this.springConfigurationProperties = springConfigurationProperties; + } + + @Override + public void validate(Transaction transaction) { + TransactionOutModel transactionModel = TransactionOutModel.fromDomain(transaction); + try { + log.info("sending to Anfifraud, transaction id:{}", transactionModel.getId() ); + this.kafkaTemplate.send(springConfigurationProperties.getKafka().getTopic().getAntifraud(), transactionModel); + this.kafkaTemplate.flush(); + } catch (Exception e) { + log.error("Occurred an error in kafka ", e); + throw new KafkaException(ErrorCode.KAFKA_EXCEPTION); + } + + } +} diff --git a/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/model/TransactionOutModel.java b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/model/TransactionOutModel.java new file mode 100644 index 0000000..7ac5925 --- /dev/null +++ b/microservices/transaction-ms/src/main/java/com/yape/transaction/infra/out/adapter/kafka/model/TransactionOutModel.java @@ -0,0 +1,33 @@ +package com.yape.transaction.infra.out.adapter.kafka.model; + +import com.yape.transaction.domain.Transaction; +import com.yape.transaction.domain.type.TransactionStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.UUID; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TransactionOutModel implements Serializable { + private Long id; + private UUID code; + private TransactionStatusEnum status; + private BigDecimal value; + + public static TransactionOutModel fromDomain(Transaction transaction) { + return TransactionOutModel.builder() + .id(transaction.getId()) + .code(transaction.getCode()) + .status(transaction.getStatus()) + .value(transaction.getValue()) + .build(); + } + + } diff --git a/microservices/transaction-ms/src/main/resources/application.properties b/microservices/transaction-ms/src/main/resources/application.properties new file mode 100644 index 0000000..cbc9eb7 --- /dev/null +++ b/microservices/transaction-ms/src/main/resources/application.properties @@ -0,0 +1,21 @@ +spring.application.name=transaction-ms + +spring.datasource.driver-class-name=org.postgresql.Driver +#docker +spring.datasource.url=jdbc:postgresql://localhost:5432/yapebd + +spring.datasource.username=postgres +spring.datasource.password=postgres +spring.datasource.initialize=true +spring.jpa.hibernate.ddl-auto=none +spring.kafka.bootstrap-servers=localhost:9092 +spring.kafka.topic.antifraud=topic-antifraud-review +spring.kafka.topic.transaction=topic-transaction-update +spring.kafka.producer.properties.spring.json.add.type.headers=false +spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer +spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer +spring.kafka.consumer.group-id=bcp-group + +spring.jpa.database=POSTGRESQL +spring.datasource.platform=postgres +spring.jpa.show-sql=true diff --git a/microservices/transaction-ms/src/test/java/com/yape/transaction/TransactionMsApplicationTests.java b/microservices/transaction-ms/src/test/java/com/yape/transaction/TransactionMsApplicationTests.java new file mode 100644 index 0000000..b11b0ef --- /dev/null +++ b/microservices/transaction-ms/src/test/java/com/yape/transaction/TransactionMsApplicationTests.java @@ -0,0 +1,13 @@ +package com.yape.transaction; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TransactionMsApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/script-bd/script-create_table.sql b/script-bd/script-create_table.sql new file mode 100644 index 0000000..66379f5 --- /dev/null +++ b/script-bd/script-create_table.sql @@ -0,0 +1,18 @@ +CREATE TABLE transaction +( + "id" BIGSERIAL PRIMARY KEY, + "code" UUID NOT NULL, + "account_external_id_debit" UUID NOT NULL, + "account_external_id_credit" UUID NOT NULL, + "value" NUMERIC NOT NULL, + "tranfer_type" VARCHAR(50) NOT NULL, + "type" VARCHAR(50) NOT NULL, + "status" VARCHAR(50) NOT NULL, + "created_at" TIMESTAMP NOT NULL, + "updated_at" TIMESTAMP, + CONSTRAINT "code_unique" UNIQUE (code) +); + +ALTER TABLE transaction ADD CONSTRAINT status_check check(status in('PENDING', 'APPROVED', 'REJECTED')); +ALTER TABLE transaction ADD CONSTRAINT transfer_type_check check(tranfer_type in('ONUS', 'INTERBANK')); +ALTER TABLE transaction ADD CONSTRAINT type_check check(type in('DEPOSIT', 'WITHDRAW', 'TRANSFER')); \ No newline at end of file