From 7aaa479222133dde5322d4b0998c953e0eb80087 Mon Sep 17 00:00:00 2001 From: jonatan Date: Thu, 5 Mar 2026 18:21:47 -0500 Subject: [PATCH 1/2] [feat] se suben los proyecto api-transaction y procces-antifraud --- api-transactions/.gitattributes | 2 + api-transactions/.gitignore | 33 ++ .../.mvn/wrapper/maven-wrapper.properties | 3 + api-transactions/Dockerfile | 13 + api-transactions/mvnw | 295 ++++++++++++++++++ api-transactions/mvnw.cmd | 189 +++++++++++ api-transactions/pom.xml | 76 +++++ .../ApiTransactionApplication.java | 13 + .../commons/TransactionStatus.java | 30 ++ .../transaction/commons/TransactionType.java | 31 ++ .../joma/transaction/config/kafkaConfig.java | 32 ++ .../controller/TransactionController.java | 32 ++ .../transaction/dto/TransactionRequest.java | 11 + .../transaction/dto/TransactionResponse.java | 10 + .../joma/transaction/entity/Transaction.java | 20 ++ .../entity/TransactionEventSource.java | 21 ++ .../repository/TransactionEventRepo.java | 10 + .../repository/TransactionRepo.java | 13 + .../service/TransactionService.java | 74 +++++ .../src/main/resources/application.properties | 10 + .../ApiTransactionApplicationTests.java | 13 + docker-compose.yml | 8 +- postgres/init.sql | 21 ++ process-antifraud/.gitattributes | 2 + process-antifraud/.gitignore | 33 ++ .../.mvn/wrapper/maven-wrapper.properties | 3 + process-antifraud/Dockerfile | 13 + process-antifraud/mvnw | 295 ++++++++++++++++++ process-antifraud/mvnw.cmd | 189 +++++++++++ process-antifraud/pom.xml | 77 +++++ .../ProcessAntifraudApplication.java | 15 + .../antifraud/adapter/AntiFraudConsumer.java | 34 ++ .../antifraud/commons/TransactionStatus.java | 30 ++ .../antifraud/commons/TransactionType.java | 31 ++ .../joma/antifraud/config/kafkaConfig.java | 39 +++ .../joma/antifraud/entity/Transaction.java | 20 ++ .../entity/TransactionEventSource.java | 22 ++ .../repository/TransactionEventRepo.java | 9 + .../antifraud/repository/TransactionRepo.java | 28 ++ .../joma/antifraud/service/AFraudService.java | 87 ++++++ .../src/main/resources/application.properties | 12 + .../ProcessAntifraudApplicationTests.java | 13 + 42 files changed, 1910 insertions(+), 2 deletions(-) create mode 100644 api-transactions/.gitattributes create mode 100644 api-transactions/.gitignore create mode 100644 api-transactions/.mvn/wrapper/maven-wrapper.properties create mode 100644 api-transactions/Dockerfile create mode 100644 api-transactions/mvnw create mode 100644 api-transactions/mvnw.cmd create mode 100644 api-transactions/pom.xml create mode 100644 api-transactions/src/main/java/com/joma/transaction/ApiTransactionApplication.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/commons/TransactionStatus.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/commons/TransactionType.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/config/kafkaConfig.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/controller/TransactionController.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/dto/TransactionRequest.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/dto/TransactionResponse.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/entity/Transaction.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/entity/TransactionEventSource.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/repository/TransactionEventRepo.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/repository/TransactionRepo.java create mode 100644 api-transactions/src/main/java/com/joma/transaction/service/TransactionService.java create mode 100644 api-transactions/src/main/resources/application.properties create mode 100644 api-transactions/src/test/java/com/joma/transaction/ApiTransactionApplicationTests.java create mode 100644 postgres/init.sql create mode 100644 process-antifraud/.gitattributes create mode 100644 process-antifraud/.gitignore create mode 100644 process-antifraud/.mvn/wrapper/maven-wrapper.properties create mode 100644 process-antifraud/Dockerfile create mode 100644 process-antifraud/mvnw create mode 100644 process-antifraud/mvnw.cmd create mode 100644 process-antifraud/pom.xml create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/ProcessAntifraudApplication.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/adapter/AntiFraudConsumer.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionStatus.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionType.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/config/kafkaConfig.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/entity/Transaction.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/entity/TransactionEventSource.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionEventRepo.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionRepo.java create mode 100644 process-antifraud/src/main/java/com/joma/antifraud/service/AFraudService.java create mode 100644 process-antifraud/src/main/resources/application.properties create mode 100644 process-antifraud/src/test/java/com/joma/antifraud/ProcessAntifraudApplicationTests.java diff --git a/api-transactions/.gitattributes b/api-transactions/.gitattributes new file mode 100644 index 0000000000..3b41682ac5 --- /dev/null +++ b/api-transactions/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/api-transactions/.gitignore b/api-transactions/.gitignore new file mode 100644 index 0000000000..667aaef0c8 --- /dev/null +++ b/api-transactions/.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/api-transactions/.mvn/wrapper/maven-wrapper.properties b/api-transactions/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..8dea6c227c --- /dev/null +++ b/api-transactions/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/api-transactions/Dockerfile b/api-transactions/Dockerfile new file mode 100644 index 0000000000..c5ae1a3392 --- /dev/null +++ b/api-transactions/Dockerfile @@ -0,0 +1,13 @@ +# Build stage +FROM maven:3.9.6-eclipse-temurin-21 AS builder +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn clean package -DskipTests + +# Runtime stage +FROM eclipse-temurin:21-jdk +WORKDIR /app +COPY --from=builder /app/target/*.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java","-jar","app.jar"] \ No newline at end of file diff --git a/api-transactions/mvnw b/api-transactions/mvnw new file mode 100644 index 0000000000..bd8896bf22 --- /dev/null +++ b/api-transactions/mvnw @@ -0,0 +1,295 @@ +#!/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 +# +# http://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.4 +# +# 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 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.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${scriptName#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_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/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 + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/api-transactions/mvnw.cmd b/api-transactions/mvnw.cmd new file mode 100644 index 0000000000..92450f9327 --- /dev/null +++ b/api-transactions/mvnw.cmd @@ -0,0 +1,189 @@ +<# : 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 http://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.4 +@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 -eq $False) { "/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_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::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 + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -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/api-transactions/pom.xml b/api-transactions/pom.xml new file mode 100644 index 0000000000..2673e8ce30 --- /dev/null +++ b/api-transactions/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.3 + + + com.joma + api-transaction + 0.0.1-SNAPSHOT + api-transaction + Api transaction + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.postgresql + r2dbc-postgresql + 1.1.1.RELEASE + compile + + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + + org.springframework.kafka + spring-kafka + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/api-transactions/src/main/java/com/joma/transaction/ApiTransactionApplication.java b/api-transactions/src/main/java/com/joma/transaction/ApiTransactionApplication.java new file mode 100644 index 0000000000..8c6d0740b3 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/ApiTransactionApplication.java @@ -0,0 +1,13 @@ +package com.joma.transaction; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ApiTransactionApplication { + + public static void main(String[] args) { + SpringApplication.run(ApiTransactionApplication.class, args); + } + +} diff --git a/api-transactions/src/main/java/com/joma/transaction/commons/TransactionStatus.java b/api-transactions/src/main/java/com/joma/transaction/commons/TransactionStatus.java new file mode 100644 index 0000000000..b2ea6215e8 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/commons/TransactionStatus.java @@ -0,0 +1,30 @@ +package com.joma.transaction.commons; + +import java.util.Map; + +public enum TransactionStatus { + APROBADO(1), + PENDIENTE(2), + RECHAZADO(3); + + private final int code; + + TransactionStatus(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + private static final Map BY_CODE = + Map.of(1, APROBADO, 2, PENDIENTE, 3, RECHAZADO); + + public static TransactionStatus fromCode(int code) { + TransactionStatus type = BY_CODE.get(code); + if (type == null) { + throw new IllegalArgumentException("Invalid status code: " + code); + } + return type; + } +} diff --git a/api-transactions/src/main/java/com/joma/transaction/commons/TransactionType.java b/api-transactions/src/main/java/com/joma/transaction/commons/TransactionType.java new file mode 100644 index 0000000000..15f1601466 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/commons/TransactionType.java @@ -0,0 +1,31 @@ +package com.joma.transaction.commons; + +import java.util.Map; + +public enum TransactionType { + TRANSFERENCIA(1), + PAGOS(2), + COMPRAS(3), + OTROS(4); + + private final int code; + + TransactionType(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + private static final Map BY_CODE = + Map.of(1, TRANSFERENCIA, 2, PAGOS, 3, COMPRAS, 4, OTROS); + + public static TransactionType fromCode(int code) { + TransactionType status = BY_CODE.get(code); + if (status == null) { + throw new IllegalArgumentException("Invalid status code: " + code); + } + return status; + } +} diff --git a/api-transactions/src/main/java/com/joma/transaction/config/kafkaConfig.java b/api-transactions/src/main/java/com/joma/transaction/config/kafkaConfig.java new file mode 100644 index 0000000000..b8d5b3c3a3 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/config/kafkaConfig.java @@ -0,0 +1,32 @@ +package com.joma.transaction.config; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; + +import java.util.HashMap; +import java.util.Map; + + +@EnableKafka +@Configuration +public class kafkaConfig { + @Bean + public ProducerFactory producerFactory() { + Map config = new HashMap<>(); + config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return new DefaultKafkaProducerFactory<>(config); + } + + @Bean + public KafkaTemplate kafkaTemplate(ProducerFactory producerFactory) { + return new KafkaTemplate<>(producerFactory); + } +} diff --git a/api-transactions/src/main/java/com/joma/transaction/controller/TransactionController.java b/api-transactions/src/main/java/com/joma/transaction/controller/TransactionController.java new file mode 100644 index 0000000000..de4968383c --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/controller/TransactionController.java @@ -0,0 +1,32 @@ +package com.joma.transaction.controller; + +import com.joma.transaction.dto.TransactionRequest; +import com.joma.transaction.dto.TransactionResponse; +import com.joma.transaction.entity.Transaction; +import com.joma.transaction.service.TransactionService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + + +@RestController +@RequestMapping("/transactions") +public class TransactionController { + private final TransactionService service; + + public TransactionController(TransactionService service) { + this.service = service; + } + + @GetMapping("/{id}") + public Mono getTransaction(@PathVariable String id) { + return service.getById(id); + } + + @PostMapping + public ResponseEntity createTransaction(@RequestBody TransactionRequest request) { + TransactionResponse response = service.createTransaction(request); + return ResponseEntity.ok(response); + } + +} diff --git a/api-transactions/src/main/java/com/joma/transaction/dto/TransactionRequest.java b/api-transactions/src/main/java/com/joma/transaction/dto/TransactionRequest.java new file mode 100644 index 0000000000..8b1d953ef8 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/dto/TransactionRequest.java @@ -0,0 +1,11 @@ +package com.joma.transaction.dto; + +import java.math.BigDecimal; + +public record TransactionRequest( + String accountExternalIdDebit, + String accountExternalIdCredit, + int transferTypeId, + BigDecimal amount +) { +} diff --git a/api-transactions/src/main/java/com/joma/transaction/dto/TransactionResponse.java b/api-transactions/src/main/java/com/joma/transaction/dto/TransactionResponse.java new file mode 100644 index 0000000000..df3bc407a3 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/dto/TransactionResponse.java @@ -0,0 +1,10 @@ +package com.joma.transaction.dto; + +import java.time.Instant; + +public record TransactionResponse( + String transactionExternalId, + String transactionName, + Instant createdAt +) { +} diff --git a/api-transactions/src/main/java/com/joma/transaction/entity/Transaction.java b/api-transactions/src/main/java/com/joma/transaction/entity/Transaction.java new file mode 100644 index 0000000000..e3cc0e28b8 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/entity/Transaction.java @@ -0,0 +1,20 @@ +package com.joma.transaction.entity; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +import java.math.BigDecimal; +import java.time.Instant; + +@Table("transactions") +public record Transaction( + @Id + String transactionExternalId, + String accountExternalIdDebit, + String accountExternalIdCredit, + String transactionName, //1=transferencia,2=pagos,3=compras + BigDecimal amount, + String transactionStatus, + Instant createdAt +) { +} \ No newline at end of file diff --git a/api-transactions/src/main/java/com/joma/transaction/entity/TransactionEventSource.java b/api-transactions/src/main/java/com/joma/transaction/entity/TransactionEventSource.java new file mode 100644 index 0000000000..fce2dd0ac6 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/entity/TransactionEventSource.java @@ -0,0 +1,21 @@ +package com.joma.transaction.entity; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Table(name = "transaction_events") +public record TransactionEventSource( + @Id + String transactionExternalId, + String accountExternalIdDebit, + String accountExternalIdCredit, + String transactionName, //1=transferencia,2=pagos,3=compras + BigDecimal amount, + String transactionStatus, + String version, + LocalDateTime createdAt +) { +} \ No newline at end of file diff --git a/api-transactions/src/main/java/com/joma/transaction/repository/TransactionEventRepo.java b/api-transactions/src/main/java/com/joma/transaction/repository/TransactionEventRepo.java new file mode 100644 index 0000000000..c72298ced3 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/repository/TransactionEventRepo.java @@ -0,0 +1,10 @@ +package com.joma.transaction.repository; + +import com.joma.transaction.entity.Transaction; +import com.joma.transaction.entity.TransactionEventSource; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TransactionEventRepo extends ReactiveCrudRepository { +} diff --git a/api-transactions/src/main/java/com/joma/transaction/repository/TransactionRepo.java b/api-transactions/src/main/java/com/joma/transaction/repository/TransactionRepo.java new file mode 100644 index 0000000000..8dfe8d2f9d --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/repository/TransactionRepo.java @@ -0,0 +1,13 @@ +package com.joma.transaction.repository; + +import com.joma.transaction.entity.Transaction; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public interface TransactionRepo extends ReactiveCrudRepository { + + Mono findBytransactionExternalId(String id); + +} diff --git a/api-transactions/src/main/java/com/joma/transaction/service/TransactionService.java b/api-transactions/src/main/java/com/joma/transaction/service/TransactionService.java new file mode 100644 index 0000000000..9c881c81c7 --- /dev/null +++ b/api-transactions/src/main/java/com/joma/transaction/service/TransactionService.java @@ -0,0 +1,74 @@ +package com.joma.transaction.service; + +import com.joma.transaction.commons.TransactionStatus; +import com.joma.transaction.commons.TransactionType; +import com.joma.transaction.dto.TransactionRequest; +import com.joma.transaction.dto.TransactionResponse; +import com.joma.transaction.entity.Transaction; +import com.joma.transaction.repository.TransactionRepo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import tools.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.util.UUID; + +@Service +public class TransactionService { + private static final Logger log = LoggerFactory.getLogger(TransactionService.class); + + private final TransactionRepo repo; + + private final KafkaTemplate kafkaTemplate; + private final ObjectMapper objectMapper; + + private static final String TOPIC = "transaction-topic"; + + public TransactionService(TransactionRepo repository, KafkaTemplate kafkaTemplate, ObjectMapper objectMapper) { + this.repo = repository; + this.kafkaTemplate = kafkaTemplate; + this.objectMapper = objectMapper; + } + + /** + * busca en la tabla read, el evento por id de transaccion generada (uuid) + * + * @param id + * @return + */ + public Mono getById(String id) { + return repo.findBytransactionExternalId(id); + } + + /** + * se crea la transaccion y deja el evento en una cola + * + * @param request + * @return + */ + public TransactionResponse createTransaction(TransactionRequest request) { + String uuid = UUID.randomUUID().toString(); + Instant now = Instant.now(); + TransactionType tt = TransactionType.fromCode(request.transferTypeId()); + + Transaction transaction = new Transaction( + uuid, request.accountExternalIdDebit(), request.accountExternalIdCredit(), tt.name(), + request.amount(), TransactionStatus.PENDIENTE.name(), now + ); + String payload = objectMapper.writeValueAsString(transaction); + kafkaTemplate.send(TOPIC, payload) + .thenAccept(result -> log.debug("Enviado:Trx: {}", uuid)) + .exceptionally(ex -> { + //reenvio a cola DLQ + return null; + }); + return new TransactionResponse( + uuid, + tt.name(), + now + ); + } +} diff --git a/api-transactions/src/main/resources/application.properties b/api-transactions/src/main/resources/application.properties new file mode 100644 index 0000000000..4840377076 --- /dev/null +++ b/api-transactions/src/main/resources/application.properties @@ -0,0 +1,10 @@ +spring.application.name=api-transaction + +spring.r2dbc.url=r2dbc:postgresql://localhost:5432/transactionsdb +spring.r2dbc.username=postgres +spring.r2dbc.password=postgres + +spring.r2dbc.pool.enabled=true +spring.r2dbc.pool.initial-size=20 +spring.r2dbc.pool.max-size=100 +spring.r2dbc.pool.max-idle-time=30m diff --git a/api-transactions/src/test/java/com/joma/transaction/ApiTransactionApplicationTests.java b/api-transactions/src/test/java/com/joma/transaction/ApiTransactionApplicationTests.java new file mode 100644 index 0000000000..f79f24469e --- /dev/null +++ b/api-transactions/src/test/java/com/joma/transaction/ApiTransactionApplicationTests.java @@ -0,0 +1,13 @@ +package com.joma.transaction; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApiTransactionApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/docker-compose.yml b/docker-compose.yml index 0e8807f21c..ccf8f9cdcb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3.7" +version: "3.8" services: postgres: image: postgres:14 @@ -7,6 +7,9 @@ services: environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=transactionsdb + volumes: + - ./postgres:/docker-entrypoint-initdb.d zookeeper: image: confluentinc/cp-zookeeper:5.5.3 environment: @@ -21,5 +24,6 @@ services: KAFKA_BROKER_ID: 1 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_JMX_PORT: 9991 + KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true" ports: - - 9092:9092 + - 9092:9092 \ No newline at end of file diff --git a/postgres/init.sql b/postgres/init.sql new file mode 100644 index 0000000000..aa236a5d4e --- /dev/null +++ b/postgres/init.sql @@ -0,0 +1,21 @@ +CREATE TABLE transactions ( + transaction_external_id VARCHAR(50) PRIMARY KEY, + account_external_id_debit VARCHAR(50), + account_external_id_credit VARCHAR(50), + transaction_name VARCHAR(50), + amount NUMERIC(18,2), + transaction_status VARCHAR(50), + created_at TIMESTAMP +); + +CREATE TABLE transaction_events ( + id SERIAL PRIMARY KEY, + transaction_external_id VARCHAR(50), + account_external_id_debit VARCHAR(50), + account_external_id_credit VARCHAR(50), + transaction_name VARCHAR(50), + amount NUMERIC(18,2), + transaction_status VARCHAR(50), + version INTEGER, + created_at TIMESTAMP +); \ No newline at end of file diff --git a/process-antifraud/.gitattributes b/process-antifraud/.gitattributes new file mode 100644 index 0000000000..3b41682ac5 --- /dev/null +++ b/process-antifraud/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/process-antifraud/.gitignore b/process-antifraud/.gitignore new file mode 100644 index 0000000000..667aaef0c8 --- /dev/null +++ b/process-antifraud/.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/process-antifraud/.mvn/wrapper/maven-wrapper.properties b/process-antifraud/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..8dea6c227c --- /dev/null +++ b/process-antifraud/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/process-antifraud/Dockerfile b/process-antifraud/Dockerfile new file mode 100644 index 0000000000..7b060942ca --- /dev/null +++ b/process-antifraud/Dockerfile @@ -0,0 +1,13 @@ +# Build stage +FROM maven:3.9.6-eclipse-temurin-21 AS builder +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn clean package -DskipTests + +# Runtime stage +FROM eclipse-temurin:21-jdk +WORKDIR /app +COPY --from=builder /app/target/*.jar app.jar +EXPOSE 8081 +ENTRYPOINT ["java","-jar","app.jar"] \ No newline at end of file diff --git a/process-antifraud/mvnw b/process-antifraud/mvnw new file mode 100644 index 0000000000..bd8896bf22 --- /dev/null +++ b/process-antifraud/mvnw @@ -0,0 +1,295 @@ +#!/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 +# +# http://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.4 +# +# 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 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.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${scriptName#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_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/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 + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/process-antifraud/mvnw.cmd b/process-antifraud/mvnw.cmd new file mode 100644 index 0000000000..92450f9327 --- /dev/null +++ b/process-antifraud/mvnw.cmd @@ -0,0 +1,189 @@ +<# : 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 http://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.4 +@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 -eq $False) { "/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_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::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 + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -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/process-antifraud/pom.xml b/process-antifraud/pom.xml new file mode 100644 index 0000000000..df8a6046e2 --- /dev/null +++ b/process-antifraud/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.3 + + + com.joma + process-antifraud + 0.0.1-SNAPSHOT + process-antifraud + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.postgresql + r2dbc-postgresql + 1.1.1.RELEASE + compile + + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + + org.springframework.kafka + spring-kafka + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/process-antifraud/src/main/java/com/joma/antifraud/ProcessAntifraudApplication.java b/process-antifraud/src/main/java/com/joma/antifraud/ProcessAntifraudApplication.java new file mode 100644 index 0000000000..801f67ed44 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/ProcessAntifraudApplication.java @@ -0,0 +1,15 @@ +package com.joma.antifraud; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.kafka.annotation.EnableKafka; + +@EnableKafka +@SpringBootApplication +public class ProcessAntifraudApplication { + + public static void main(String[] args) { + SpringApplication.run(ProcessAntifraudApplication.class, args); + } + +} diff --git a/process-antifraud/src/main/java/com/joma/antifraud/adapter/AntiFraudConsumer.java b/process-antifraud/src/main/java/com/joma/antifraud/adapter/AntiFraudConsumer.java new file mode 100644 index 0000000000..825ee98a62 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/adapter/AntiFraudConsumer.java @@ -0,0 +1,34 @@ +package com.joma.antifraud.adapter; + +import com.joma.antifraud.entity.Transaction; +import com.joma.antifraud.service.AFraudService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Service; +import tools.jackson.databind.ObjectMapper; + +@Service +public class AntiFraudConsumer { + private static final Logger log = LoggerFactory.getLogger(AntiFraudConsumer.class); + + private final ObjectMapper objectMapper; + private final AFraudService fraudService; + + public AntiFraudConsumer(ObjectMapper objectMapper, AFraudService fraudService) { + this.objectMapper = objectMapper; + this.fraudService = fraudService; + } + + + @KafkaListener(topics = "transaction-topic", groupId = "antifraud-group", autoStartup = "true") + public void consume(String message) { + try { + Transaction eventMessage = objectMapper.readValue(message, Transaction.class); + fraudService.processTransaction(eventMessage).subscribe(); + + } catch (Exception e) { + log.error("Error en lectura de mensajes: {}", e.toString()); + } + } +} diff --git a/process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionStatus.java b/process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionStatus.java new file mode 100644 index 0000000000..fb8e9037d1 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionStatus.java @@ -0,0 +1,30 @@ +package com.joma.antifraud.commons; + +import java.util.Map; + +public enum TransactionStatus { + APROBADO(1), + PENDIENTE(2), + RECHAZADO(3); + + private final int code; + + TransactionStatus(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + private static final Map BY_CODE = + Map.of(1, APROBADO, 2, PENDIENTE, 3, RECHAZADO); + + public static TransactionStatus fromCode(int code) { + TransactionStatus type = BY_CODE.get(code); + if (type == null) { + throw new IllegalArgumentException("StatusCode invalido: " + code); + } + return type; + } +} diff --git a/process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionType.java b/process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionType.java new file mode 100644 index 0000000000..ef41642d3b --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/commons/TransactionType.java @@ -0,0 +1,31 @@ +package com.joma.antifraud.commons; + +import java.util.Map; + +public enum TransactionType { + TRANSFERENCIA(1), + PAGOS(2), + COMPRAS(3), + OTROS(4); + + private final int code; + + TransactionType(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + private static final Map BY_CODE = + Map.of(1, TRANSFERENCIA, 2, PAGOS, 3, COMPRAS, 4, OTROS); + + public static TransactionType fromCode(int code) { + TransactionType status = BY_CODE.get(code); + if (status == null) { + throw new IllegalArgumentException("TypeCode Invalido: " + code); + } + return status; + } +} diff --git a/process-antifraud/src/main/java/com/joma/antifraud/config/kafkaConfig.java b/process-antifraud/src/main/java/com/joma/antifraud/config/kafkaConfig.java new file mode 100644 index 0000000000..7cfa1235d2 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/config/kafkaConfig.java @@ -0,0 +1,39 @@ +package com.joma.antifraud.config; + +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 java.util.HashMap; +import java.util.Map; + +@EnableKafka +@Configuration +public class kafkaConfig { + + @Bean + public ConsumerFactory consumerFactory() { + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "antifraud-group"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + return new DefaultKafkaConsumerFactory<>(props); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory( + ConsumerFactory consumerFactory) { + + ConcurrentKafkaListenerContainerFactory factory = + new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory); + factory.setConcurrency(4); + return factory; + } +} diff --git a/process-antifraud/src/main/java/com/joma/antifraud/entity/Transaction.java b/process-antifraud/src/main/java/com/joma/antifraud/entity/Transaction.java new file mode 100644 index 0000000000..1fd819b0e9 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/entity/Transaction.java @@ -0,0 +1,20 @@ +package com.joma.antifraud.entity; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +import java.math.BigDecimal; +import java.time.Instant; + +@Table("transactions") +public record Transaction( + @Id + String transactionExternalId, + String accountExternalIdDebit, + String accountExternalIdCredit, + String transactionName, //1=transferencia,2=pagos,3=compras + BigDecimal amount, + String transactionStatus, + Instant createdAt +) { +} \ No newline at end of file diff --git a/process-antifraud/src/main/java/com/joma/antifraud/entity/TransactionEventSource.java b/process-antifraud/src/main/java/com/joma/antifraud/entity/TransactionEventSource.java new file mode 100644 index 0000000000..ef610f1018 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/entity/TransactionEventSource.java @@ -0,0 +1,22 @@ +package com.joma.antifraud.entity; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +import java.math.BigDecimal; +import java.time.Instant; + +@Table(name = "transaction_events") +public record TransactionEventSource( + @Id + Long id, + String transactionExternalId, + String accountExternalIdDebit, + String accountExternalIdCredit, + String transactionName, //1=transferencia,2=pagos,3=compras + BigDecimal amount, + String transactionStatus, + Integer version, + Instant createdAt +) { +} \ No newline at end of file diff --git a/process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionEventRepo.java b/process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionEventRepo.java new file mode 100644 index 0000000000..49c7b29b87 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionEventRepo.java @@ -0,0 +1,9 @@ +package com.joma.antifraud.repository; + +import com.joma.antifraud.entity.TransactionEventSource; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TransactionEventRepo extends ReactiveCrudRepository { +} diff --git a/process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionRepo.java b/process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionRepo.java new file mode 100644 index 0000000000..f93ae4113d --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/repository/TransactionRepo.java @@ -0,0 +1,28 @@ +package com.joma.antifraud.repository; + +import com.joma.antifraud.entity.Transaction; +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public interface TransactionRepo extends ReactiveCrudRepository { + + @Query("INSERT INTO transactions (" + + "transaction_external_id, " + + "account_external_id_debit, " + + "account_external_id_credit, " + + "transaction_name, " + + "amount, " + + "transaction_status, " + + "created_at) " + + "VALUES (:#{#t.transactionExternalId}, " + + ":#{#t.accountExternalIdDebit}, " + + ":#{#t.accountExternalIdCredit}, " + + ":#{#t.transactionName}, " + + ":#{#t.amount}, " + + ":#{#t.transactionStatus}, " + + ":#{#t.createdAt})") + Mono insertTransaction(Transaction t); +} diff --git a/process-antifraud/src/main/java/com/joma/antifraud/service/AFraudService.java b/process-antifraud/src/main/java/com/joma/antifraud/service/AFraudService.java new file mode 100644 index 0000000000..26f72e49a1 --- /dev/null +++ b/process-antifraud/src/main/java/com/joma/antifraud/service/AFraudService.java @@ -0,0 +1,87 @@ +package com.joma.antifraud.service; + +import com.joma.antifraud.commons.TransactionStatus; +import com.joma.antifraud.entity.Transaction; +import com.joma.antifraud.entity.TransactionEventSource; +import com.joma.antifraud.repository.TransactionEventRepo; +import com.joma.antifraud.repository.TransactionRepo; +import org.springframework.stereotype.Service; +import org.springframework.transaction.reactive.TransactionalOperator; +import reactor.core.publisher.Mono; + +import java.math.BigDecimal; +import java.time.Instant; + +@Service +public class AFraudService { + + final private TransactionRepo transRepo; + final private TransactionEventRepo transRepoEvent; + private final TransactionalOperator transactionalOperator; + + public AFraudService(TransactionRepo transRepo, TransactionEventRepo transRepoEvent, TransactionalOperator transactionalOperator) { + this.transRepo = transRepo; + this.transRepoEvent = transRepoEvent; + this.transactionalOperator = transactionalOperator; + } + + /** + * operacion principal que opera la transaccion, primero insert en tabla read y write, luego evalua y finalmente actualiza estados (aprobado, rechazado) + * @param message + * @return + */ + public Mono processTransaction(Transaction message) { + return transactionalOperator.execute(tx -> + transRepo.insertTransaction(message) + .then(insertEvent(message, 1)) + .then(evaluateAndUpdate(message))) + .then(); + } + + /** + * guarda los eventos en la tabla write, esta tabla sera un repositorio de eventos + * @param msg + * @param version + * @return + */ + private Mono insertEvent(Transaction msg, int version) { + TransactionEventSource event = new TransactionEventSource( + null, + msg.transactionExternalId(), + msg.accountExternalIdDebit(), + msg.accountExternalIdCredit(), + msg.transactionName(), + msg.amount(), + msg.transactionStatus(), + version, + Instant.now() + ); + return transRepoEvent.save(event).then(); + } + + /** + * evalua la transaccion y actualiza los estados en la tabla read y write (event sourcing) + * @param message + * @return + */ + private Mono evaluateAndUpdate(Transaction message) { + + TransactionStatus finalStatus = + message.amount().compareTo(BigDecimal.valueOf(1000)) > 0 + ? TransactionStatus.RECHAZADO + : TransactionStatus.APROBADO; + + Transaction updated = new Transaction( + message.transactionExternalId(), + message.accountExternalIdDebit(), + message.accountExternalIdCredit(), + message.transactionName(), + message.amount(), + finalStatus.name(), + message.createdAt() + ); + + return transRepo.save(updated) + .then(insertEvent(updated, 2)); + } +} diff --git a/process-antifraud/src/main/resources/application.properties b/process-antifraud/src/main/resources/application.properties new file mode 100644 index 0000000000..43780e0ef9 --- /dev/null +++ b/process-antifraud/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.application.name=process-antifraud +spring.r2dbc.url=r2dbc:postgresql://localhost:5432/transactionsdb +spring.r2dbc.username=postgres +spring.r2dbc.password=postgres +spring.r2dbc.pool.enabled=true +spring.r2dbc.pool.initial-size=20 +spring.r2dbc.pool.max-size=100 +spring.r2dbc.pool.max-idle-time=30m +spring.threads.virtual.enabled=true +server.port=8081 +#logging.level.org.springframework.r2dbc=DEBUG +#logging.level.io.r2dbc.postgresql=DEBUG \ No newline at end of file diff --git a/process-antifraud/src/test/java/com/joma/antifraud/ProcessAntifraudApplicationTests.java b/process-antifraud/src/test/java/com/joma/antifraud/ProcessAntifraudApplicationTests.java new file mode 100644 index 0000000000..a86aae3816 --- /dev/null +++ b/process-antifraud/src/test/java/com/joma/antifraud/ProcessAntifraudApplicationTests.java @@ -0,0 +1,13 @@ +package com.joma.antifraud; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ProcessAntifraudApplicationTests { + + @Test + void contextLoads() { + } + +} From dc5a5a473e996efc067f058bb760f4e4e2ca300e Mon Sep 17 00:00:00 2001 From: jonatan Date: Thu, 5 Mar 2026 18:37:59 -0500 Subject: [PATCH 2/2] [feat] se sube arquitectura --- arquitectura/Arquitecturav4.png | Bin 0 -> 65251 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 arquitectura/Arquitecturav4.png diff --git a/arquitectura/Arquitecturav4.png b/arquitectura/Arquitecturav4.png new file mode 100644 index 0000000000000000000000000000000000000000..7567569b658bad06e511bd3a008c90955f1f89cb GIT binary patch literal 65251 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 ELV@=zs>W=j3 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 aJ9mFa+8}&EjEk#*a($1Bbp|g?; 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 X>`HvhNq*J`=YddF)KsXDGY|Yf=067% literal 0 HcmV?d00001