diff --git a/README.md b/README.md index 81f17ce..9098978 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # BIAN-Quarkus -Repositorio de APIs BIAN implementadas con Quarkus \ No newline at end of file +Repositorio de APIs BIAN de consulta de productos. Con implementación del mock para la consulta de TDD. \ No newline at end of file diff --git a/bus-evaluate-customer-product/.dockerignore b/bus-evaluate-customer-product/.dockerignore new file mode 100644 index 0000000..94810d0 --- /dev/null +++ b/bus-evaluate-customer-product/.dockerignore @@ -0,0 +1,5 @@ +* +!target/*-runner +!target/*-runner.jar +!target/lib/* +!target/quarkus-app/* \ No newline at end of file diff --git a/bus-evaluate-customer-product/.gitignore b/bus-evaluate-customer-product/.gitignore new file mode 100644 index 0000000..91a800a --- /dev/null +++ b/bus-evaluate-customer-product/.gitignore @@ -0,0 +1,45 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ +# TLS Certificates +.certs/ diff --git a/bus-evaluate-customer-product/.mvn/wrapper/.gitignore b/bus-evaluate-customer-product/.mvn/wrapper/.gitignore new file mode 100644 index 0000000..e72f5e8 --- /dev/null +++ b/bus-evaluate-customer-product/.mvn/wrapper/.gitignore @@ -0,0 +1 @@ +maven-wrapper.jar diff --git a/bus-evaluate-customer-product/.mvn/wrapper/MavenWrapperDownloader.java b/bus-evaluate-customer-product/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..fe7d037 --- /dev/null +++ b/bus-evaluate-customer-product/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,93 @@ +/* + * 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. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.ThreadLocalRandom; + +public final class MavenWrapperDownloader { + private static final String WRAPPER_VERSION = "3.3.2"; + + private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); + + public static void main(String[] args) { + log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION); + + if (args.length != 2) { + System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing"); + System.exit(1); + } + + try { + log(" - Downloader started"); + final URL wrapperUrl = URI.create(args[0]).toURL(); + final String jarPath = args[1].replace("..", ""); // Sanitize path + final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize(); + downloadFileFromURL(wrapperUrl, wrapperJarPath); + log("Done"); + } catch (IOException e) { + System.err.println("- Error downloading: " + e.getMessage()); + if (VERBOSE) { + e.printStackTrace(); + } + System.exit(1); + } + } + + private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath) + throws IOException { + log(" - Downloading to: " + wrapperJarPath); + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + final String username = System.getenv("MVNW_USERNAME"); + final char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + Path temp = wrapperJarPath + .getParent() + .resolve(wrapperJarPath.getFileName() + "." + + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); + try (InputStream inStream = wrapperUrl.openStream()) { + Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING); + Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING); + } finally { + Files.deleteIfExists(temp); + } + log(" - Downloader complete"); + } + + private static void log(String msg) { + if (VERBOSE) { + System.out.println(msg); + } + } + +} diff --git a/bus-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties b/bus-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..1a580be --- /dev/null +++ b/bus-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,20 @@ +# 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. +wrapperVersion=3.3.2 +distributionType=source +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar \ No newline at end of file diff --git a/bus-evaluate-customer-product/CHANGELOG.md b/bus-evaluate-customer-product/CHANGELOG.md new file mode 100644 index 0000000..dcb4852 --- /dev/null +++ b/bus-evaluate-customer-product/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +Todos los cambios notables en este proyecto se documentarán en este archivo. + +El formato se basa en [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +y este proyecto se adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v1.0.0 - UNRELEASED +### Updated +- Migración completada a Quarkus 3.25.3 \ No newline at end of file diff --git a/bus-evaluate-customer-product/README.md b/bus-evaluate-customer-product/README.md new file mode 100644 index 0000000..048d399 --- /dev/null +++ b/bus-evaluate-customer-product/README.md @@ -0,0 +1,61 @@ +# bus-evaluate-customer-product + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: . + +## Running the application in dev mode + +You can run your application in dev mode that enables live coding using: + +```shell script +./mvnw quarkus:dev +``` + +> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at . + +## Packaging and running the application + +The application can be packaged using: + +```shell script +./mvnw package +``` + +It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory. + +The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`. + +If you want to build an _über-jar_, execute the following command: + +```shell script +./mvnw package -Dquarkus.package.jar.type=uber-jar +``` + +The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`. + +## Creating a native executable + +You can create a native executable using: + +```shell script +./mvnw package -Dnative +``` + +Or, if you don't have GraalVM installed, you can run the native executable build in a container using: + +```shell script +./mvnw package -Dnative -Dquarkus.native.container-build=true +``` + +You can then execute your native executable with: `./target/bus-evaluate-customer-product-1.0.0-runner` + +If you want to learn more about building native executables, please consult . + +## Related Guides + +- Hibernate Validator ([guide](https://quarkus.io/guides/validation)): Validate object properties (field, getter) and method parameters for your beans (REST, CDI, Jakarta Persistence) +- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it +- YAML Configuration ([guide](https://quarkus.io/guides/config-yaml)): Use YAML to configure your Quarkus application +- SmallRye Health ([guide](https://quarkus.io/guides/smallrye-health)): Monitor service health diff --git a/bus-evaluate-customer-product/TASKS.md b/bus-evaluate-customer-product/TASKS.md new file mode 100644 index 0000000..5da32bf --- /dev/null +++ b/bus-evaluate-customer-product/TASKS.md @@ -0,0 +1,2 @@ +- user login de spring tiene respuesta diferente a la hecha en quarkus (migracion cargo en cuenta) +- Condicionar que el postgres guarde en cache \ No newline at end of file diff --git a/bus-evaluate-customer-product/mvnw b/bus-evaluate-customer-product/mvnw new file mode 100644 index 0000000..5e9618c --- /dev/null +++ b/bus-evaluate-customer-product/mvnw @@ -0,0 +1,332 @@ +#!/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.2 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /usr/local/etc/mavenrc ]; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)" + export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home" + export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ + && JAVA_HOME="$( + cd "$JAVA_HOME" || ( + echo "cannot cd into $JAVA_HOME." >&2 + exit 1 + ) + pwd + )" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin; then + javaHome="$(dirname "$javaExecutable")" + javaExecutable="$(cd "$javaHome" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "$javaExecutable")" + fi + javaHome="$(dirname "$javaExecutable")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + 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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$( + \unset -f command 2>/dev/null + \command -v java + )" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." >&2 +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" >&2 + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." || exit 1 + pwd + ) + fi + # end of workaround + done + printf '%s' "$( + cd "$basedir" || exit 1 + pwd + )" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' <"$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in wrapperUrl) + wrapperUrl="$safeValue" + break + ;; + esac + done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in wrapperSha256Sum) + wrapperSha256Sum=$value + break + ;; + esac +done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then + wrapperSha256Result=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 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] \ + && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/bus-evaluate-customer-product/mvnw.cmd b/bus-evaluate-customer-product/mvnw.cmd new file mode 100644 index 0000000..4136715 --- /dev/null +++ b/bus-evaluate-customer-product/mvnw.cmd @@ -0,0 +1,206 @@ +@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.2 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. >&2 +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. >&2 +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/bus-evaluate-customer-product/pom.xml b/bus-evaluate-customer-product/pom.xml new file mode 100644 index 0000000..6a51abd --- /dev/null +++ b/bus-evaluate-customer-product/pom.xml @@ -0,0 +1,226 @@ + + + 4.0.0 + com.banesco + bus-evaluate-customer-product + bus-evaluate-customer-product + API REST Customer Product (consulta de cuentas) BUS + 1.0.0 + + + 3.14.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.25.3 + true + com.banesco.Main + 3.5.3 + 4.0.4 + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + com.banesco + commons + 1.0 + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-rest-client-jackson + + + io.quarkus + quarkus-config-yaml + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-junit5 + test + + + org.projectlombok + lombok + 1.18.38 + + + io.quarkus + quarkus-redis-client + + + io.quarkus + quarkus-redis-cache + + + io.quarkiverse.cxf + quarkus-cxf + 3.15.1 + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-swagger-ui + + + io.quarkus + quarkus-smallrye-health + + + + io.quarkus + quarkus-hibernate-validator + + + + + io.quarkus + quarkus-jdbc-postgresql + + + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + + + + org.apache.cxf + cxf-codegen-plugin + ${cxf.version} + + + generate-sources + generate-sources + + ${project.build.directory}/generated-sources/cxf + + + ${project.basedir}/src/main/resources/wsdl/PruebaService.wsdl + + -client + -verbose + + + + + + wsdl2java + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + true + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/cxf + + + + + + + + + + + + native + + + native + + + + false + true + + + + diff --git a/bus-evaluate-customer-product/scripts/native/Dockerfile b/bus-evaluate-customer-product/scripts/native/Dockerfile new file mode 100644 index 0000000..6c0f979 --- /dev/null +++ b/bus-evaluate-customer-product/scripts/native/Dockerfile @@ -0,0 +1,12 @@ +FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 +RUN mkdir -p /work +ENV TZ="America/Caracas" +ENV LANGUAGE='en_US:en' +VOLUME /tmp +COPY /file/*-runner /work/busCustomerAccounts +RUN chmod -R 775 /work +RUN ls -ltra /work/ +EXPOSE 8080 +WORKDIR /work/ + +ENTRYPOINT ["./busCustomerAccounts", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/bus-evaluate-customer-product/scripts/native/file/bus-evaluate-customer-product-1.0.0-runner b/bus-evaluate-customer-product/scripts/native/file/bus-evaluate-customer-product-1.0.0-runner new file mode 100644 index 0000000..ae03720 Binary files /dev/null and b/bus-evaluate-customer-product/scripts/native/file/bus-evaluate-customer-product-1.0.0-runner differ diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.jvm b/bus-evaluate-customer-product/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..156f18c --- /dev/null +++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.jvm @@ -0,0 +1,98 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/pub-get-accounts-client-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override +# the default JVM options, use `JAVA_OPTS_APPEND` to append options +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi9/openjdk-17:1.23 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar b/bus-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar new file mode 100644 index 0000000..88079f9 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar @@ -0,0 +1,94 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package -Dquarkus.package.jar.type=legacy-jar +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/pub-get-accounts-client-legacy-jar . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-legacy-jar +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-legacy-jar +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override +# the default JVM options, use `JAVA_OPTS_APPEND` to append options +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi9/openjdk-17:1.23 + +ENV LANGUAGE='en_US:en' + + +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/quarkus-run.jar + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.native b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..de4d60d --- /dev/null +++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native @@ -0,0 +1,29 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./mvnw package -Dnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/pub-get-accounts-client . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client +# +# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.6` base image is based on UBI 9. +# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`. +### +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.6 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root --chmod=0755 target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.native-micro b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native-micro new file mode 100644 index 0000000..c1df759 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native-micro @@ -0,0 +1,32 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# It uses a micro base image, tuned for Quarkus native executables. +# It reduces the size of the resulting container image. +# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. +# +# Before building the container image run: +# +# ./mvnw package -Dnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/pub-get-accounts-client . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client +# +# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9. +# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`. +### +FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root --chmod=0755 target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/bus-evaluate-customer-product/src/main/java/Main.java b/bus-evaluate-customer-product/src/main/java/Main.java new file mode 100644 index 0000000..6dd9085 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/Main.java @@ -0,0 +1,12 @@ +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.annotations.QuarkusMain; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@QuarkusMain +public class Main { + public static void main(String[] args) { + log.info("Running bus-evaluate-customer-product..."); + Quarkus.run(args); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java b/bus-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java new file mode 100644 index 0000000..61dd76a --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java @@ -0,0 +1,12 @@ +package application.exception; + +public class GatewayServiceException extends RuntimeException { + public GatewayServiceException(String message) { + super(message); + } + + public GatewayServiceException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java b/bus-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java new file mode 100644 index 0000000..0ccf895 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java @@ -0,0 +1,7 @@ +package application.exception; + +public class InvalidTokenException extends RuntimeException { + public InvalidTokenException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java b/bus-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java new file mode 100644 index 0000000..1b55999 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java @@ -0,0 +1,7 @@ +package application.exception; + +public class ServiceCommunicationException extends GatewayServiceException { + public ServiceCommunicationException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/factory/SecurityTraceFactory.java b/bus-evaluate-customer-product/src/main/java/application/factory/SecurityTraceFactory.java new file mode 100644 index 0000000..65edf32 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/factory/SecurityTraceFactory.java @@ -0,0 +1,37 @@ +package application.factory; + +import application.port.input.command.RegisterSecurityCommand; +import domain.dto.response.StatusResponse; +import domain.model.AccountClientContext; +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +@Slf4j +@ApplicationScoped +public class SecurityTraceFactory { + + public RegisterSecurityCommand createCommandFromContext(AccountClientContext context, StatusResponse statusResponse) { + var request = context.getRequest(); + var customerReference = request.getCustomerPositionState().getCustomerReference(); + var device = request.getDevice(); + + long durationInMillis = System.currentTimeMillis() - context.getStartExecution(); + + return RegisterSecurityCommand.builder() + .login(context.getUsername()) + .fecHor(Timestamp.valueOf(LocalDateTime.now())) + .nacCli(customerReference.getCustomerIdType()) + .cedRifCli(Integer.valueOf(customerReference.getCustomerId())) + .tipoRespuesta(Integer.valueOf(statusResponse.getStatusCode())) + .msgRespuesta(statusResponse.getMessage()) + .codFintech(context.getFintechId()) + .tiempoRespuesta((int) durationInMillis) + .tipoDispositivo(device.getDeviceType()) + .desDispositivo(device.getDeviceDescription()) + .ipCli(device.getDeviceIp()) + .build(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java b/bus-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java new file mode 100644 index 0000000..2f4831d --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java @@ -0,0 +1,25 @@ +package application.helper; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@Slf4j +public class CustomerHelper { + /** + * Construye de forma segura un ID de cliente a partir de su tipo y número. + * + * @param idType El tipo de identificación (ej. "V", "J"). + * @param idNumber El número de identificación. + * @return Un {@link Optional} con el ID concatenado si ambos parámetros son válidos (no nulos ni vacíos), + * o un {@code Optional.empty()} en caso contrario. + */ + public static Optional buildCustomerId(String idType, String idNumber) { + return Optional.ofNullable(idType) + .filter(type -> !type.isBlank()) + .flatMap(type -> Optional.ofNullable(idNumber) + .filter(number -> !number.isBlank()) + .map(number -> type + number)); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java b/bus-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java new file mode 100644 index 0000000..5df09c1 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java @@ -0,0 +1,79 @@ +package application.helper; + +import domain.dto.response.CustomerPositionStateResponse; +import domain.dto.response.CustomerResponse; +import domain.dto.response.StatusResponse; +import domain.model.BankingProduct; +import domain.model.ErrorCode; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.core.Response; + +import java.util.List; + +@ApplicationScoped +public class ResponseHelper { + + private final StatusDescriptionResolver statusDescriptionResolver; + + public ResponseHelper(StatusDescriptionResolver statusDescriptionResolver) { + this.statusDescriptionResolver = statusDescriptionResolver; + } + + public Response buildErrorResponse(String errorCodeKey, String requestId) { + var errorCode = statusDescriptionResolver.getCode(errorCodeKey); + return this.buildErrorResponse(errorCode, requestId); + } + + /** + * Construye una respuesta de error estandarizada de forma genérica. + * @param errorCode Objeto con el código de error interno. + * @return Un objeto Response estándar de JAX-RS. + */ + public Response buildErrorResponse(ErrorCode errorCode, String requestId) { + + var statusResponse = StatusResponse.builder() + .status(errorCode.status()) + .statusCode(errorCode.statusCode()) + .message(errorCode.description()) + .traceId(requestId) + .build(); + + var response = new CustomerResponse(statusResponse); + + int httpStatus = errorCode.getHttpStatusCode(); + + return Response.status(httpStatus) + .entity(response) + .build(); + } + + /** + * Construye una respuesta de éxito (200) CON DATOS. + * @param customerAccounts La lista de productos (el cuerpo de la respuesta). + * @param successCode El ErrorCode "200" (para los detalles del status). + * @param requestId El ID de traza. + * @return Un objeto Response estándar de JAX-RS. + */ + public Response buildSuccessResponse(List customerAccounts, ErrorCode successCode, String requestId) { + + var status = StatusResponse.builder() + .status(successCode.status()) + .statusCode(successCode.statusCode()) + .message(successCode.description()) + .traceId(requestId) + .build(); + + var accounts = CustomerPositionStateResponse.builder() + .bankingProducts(customerAccounts) + .record(customerAccounts.size()) + .build(); + + var response = new CustomerResponse(status, accounts); + + return Response.status(successCode.getHttpStatusCode()) + .entity(response) + .build(); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IAppConfig.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IAppConfig.java new file mode 100644 index 0000000..a71f302 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IAppConfig.java @@ -0,0 +1,7 @@ +package application.port.input; + +import domain.entity.AppConfiguration; + +public interface IAppConfig { + AppConfiguration execute(String appName); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/ICustomerProduct.java b/bus-evaluate-customer-product/src/main/java/application/port/input/ICustomerProduct.java new file mode 100644 index 0000000..81acebc --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/ICustomerProduct.java @@ -0,0 +1,13 @@ +package application.port.input; + +import application.port.input.command.CustomerAccountCommand; +import domain.entity.AppConfiguration; +import domain.model.AccountClientContext; +import domain.model.BankingProduct; + +import java.util.List; + +public interface ICustomerProduct { + // TODO: mejorar + List execute(CustomerAccountCommand command, AccountClientContext context, AppConfiguration appConfig, String otp, Boolean isCacheable, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IEncodeValue.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IEncodeValue.java new file mode 100644 index 0000000..ceff2e4 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IEncodeValue.java @@ -0,0 +1,7 @@ +package application.port.input; + +import application.port.input.command.EncodeValueCommand; + +public interface IEncodeValue { + String execute(EncodeValueCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IRegisterSecurity.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IRegisterSecurity.java new file mode 100644 index 0000000..dd12f77 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IRegisterSecurity.java @@ -0,0 +1,7 @@ +package application.port.input; + +import application.port.input.command.RegisterSecurityCommand; + +public interface IRegisterSecurity { + void execute(RegisterSecurityCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IServiceStatus.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IServiceStatus.java new file mode 100644 index 0000000..9ddd810 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IServiceStatus.java @@ -0,0 +1,7 @@ +package application.port.input; + +import application.port.input.command.ServiceStatusCommand; + +public interface IServiceStatus { + Boolean execute(ServiceStatusCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IUserLogin.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IUserLogin.java new file mode 100644 index 0000000..37e4ad0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IUserLogin.java @@ -0,0 +1,13 @@ +package application.port.input; + +import application.port.input.command.UserLoginCommand; +import domain.model.UserLogin; + +import java.util.Optional; + +/** + * Puerto de entrada para el caso de uso de login de usuario (API privada) + */ +public interface IUserLogin { + Optional execute(UserLoginCommand command, String requestId); +} diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateOtp.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateOtp.java new file mode 100644 index 0000000..7d88f76 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateOtp.java @@ -0,0 +1,7 @@ +package application.port.input; + +import application.port.input.command.ValidateOtpCommand; + +public interface IValidateOtp { + Boolean isValidOtp(ValidateOtpCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateProfile.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateProfile.java new file mode 100644 index 0000000..170f3ca --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateProfile.java @@ -0,0 +1,7 @@ +package application.port.input; + +import application.port.input.command.ValidateProfileCommand; + +public interface IValidateProfile { + Boolean execute(ValidateProfileCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/CustomerAccountCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/CustomerAccountCommand.java new file mode 100644 index 0000000..97fb1ca --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/CustomerAccountCommand.java @@ -0,0 +1,25 @@ +package application.port.input.command; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class CustomerAccountCommand { + String rif; + String bankCode; + String currencyCode; + String status; + String statusMe; + String productCv; + String product; + String channel; + String service; + String operator; + String phone; + String affiliationStatus; + String counterpartyAccount; + String holder; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/EncodeValueCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/EncodeValueCommand.java new file mode 100644 index 0000000..04599c8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/EncodeValueCommand.java @@ -0,0 +1,5 @@ +package application.port.input.command; + +public record EncodeValueCommand( + String sApp +) {} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/RegisterSecurityCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/RegisterSecurityCommand.java new file mode 100644 index 0000000..b875535 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/RegisterSecurityCommand.java @@ -0,0 +1,39 @@ +package application.port.input.command; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +import java.util.Date; + +@Value +@Builder +@RegisterForReflection +public class RegisterSecurityCommand { + String login; + Date fecHor; + String nacCli; + Integer cedRifCli; + String tipoProductoCli; + String tipoProductoBen; + String productoCli; + String codEmpresa; + String nacBen; + Integer cedBen; + String nombreBen; + String productoBen; + Double monto; + String referencia; + String nroDePago; + String desPago; + String objeto; + Integer tipoRespuesta; + String msgRespuesta; + Integer tiempoRespuesta; + String codFintech; + String cedRifFintech; + String tipoDispositivo; + String desDispositivo; + String ipCli; + String sp; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/ServiceStatusCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ServiceStatusCommand.java new file mode 100644 index 0000000..30f4c3e --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ServiceStatusCommand.java @@ -0,0 +1,9 @@ +package application.port.input.command; + +import domain.model.BankService; + +public record ServiceStatusCommand( + String applicationId, + String transactionId, + BankService bankService +) { } \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/UserLoginCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/UserLoginCommand.java new file mode 100644 index 0000000..15ac2d8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/UserLoginCommand.java @@ -0,0 +1,14 @@ +package application.port.input.command; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +/** + * Contiene los datos entradas para el caso de uso de login de usuario + * @param customerId + * @param customerIdType + */ +@RegisterForReflection +public record UserLoginCommand( + String customerId, + String customerIdType +) { } \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateOtpCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateOtpCommand.java new file mode 100644 index 0000000..29a8d3a --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateOtpCommand.java @@ -0,0 +1,11 @@ +package application.port.input.command; + +public record ValidateOtpCommand( + String otpToken, + String appOrigen, + String login, + String ipAddress, + String canal, + String servicio, + String domain +) { } \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateProfileCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateProfileCommand.java new file mode 100644 index 0000000..503c688 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateProfileCommand.java @@ -0,0 +1,10 @@ +package application.port.input.command; + +/** + * Contiene los datos de entrada para el caso de uso de validate profile + */ +public record ValidateProfileCommand( + String customerId, + String ipAddress, + String hash +) {} diff --git a/bus-evaluate-customer-product/src/main/java/application/port/output/IAppConfigRepository.java b/bus-evaluate-customer-product/src/main/java/application/port/output/IAppConfigRepository.java new file mode 100644 index 0000000..86c5b79 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/output/IAppConfigRepository.java @@ -0,0 +1,5 @@ +package application.port.output; + +public interface IAppConfigRepository { + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/output/IMaintenanceCheckPort.java b/bus-evaluate-customer-product/src/main/java/application/port/output/IMaintenanceCheckPort.java new file mode 100644 index 0000000..826a981 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/output/IMaintenanceCheckPort.java @@ -0,0 +1,5 @@ +package application.port.output; + +public interface IMaintenanceCheckPort { + Boolean isUnderMaintenance(String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/port/output/RegisterSecurityUseCase.java b/bus-evaluate-customer-product/src/main/java/application/port/output/RegisterSecurityUseCase.java new file mode 100644 index 0000000..c61619e --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/port/output/RegisterSecurityUseCase.java @@ -0,0 +1,35 @@ +package application.port.output; + +import application.exception.GatewayServiceException; +import application.port.input.IRegisterSecurity; +import application.port.input.command.RegisterSecurityCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.repository.IRegisterSecurityPort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ApplicationScoped +public class RegisterSecurityUseCase implements IRegisterSecurity { + + private final IRegisterSecurityPort registerSecurityPort; + + @Inject + public RegisterSecurityUseCase(IRegisterSecurityPort registerSecurityPort) { + this.registerSecurityPort = registerSecurityPort; + } + + @Override + public void execute(RegisterSecurityCommand command, String requestId) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Registrar traza de seguridad")); + try { + registerSecurityPort.writeTrace(command, requestId); + } catch (GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de register security")); + throw ex; + } finally { + log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: Registrar traza de seguridad")); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/service/AccountService.java b/bus-evaluate-customer-product/src/main/java/application/service/AccountService.java new file mode 100644 index 0000000..b3ba549 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/service/AccountService.java @@ -0,0 +1,43 @@ +package application.service; + +import com.banesco.common.domain.model.redis.UserAccountRedis; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import com.banesco.common.infraestructure.repository.UserAccountRedisRepository; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +@ApplicationScoped +public class AccountService { + + @Inject + UserAccountRedisRepository userAccountRedisRepository; + + /** + * Guarda una lista de cuentas en la caché de Redis y establece un tiempo de expiración (TTL). + * + * @param requestId ID de la solicitud para trazabilidad en los logs. + * @param id El identificador único del cliente para la clave del hash. + * @param sid El identificador de sesión para la clave del índice. + * @param accounts La lista de cuentas a guardar. + * @param ttlInSeconds El tiempo de vida (en segundos) para las claves en caché. + */ + public void saveAccounts(String requestId, String id, String sid, List accounts, long ttlInSeconds) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando guardado en Redis para el sid", sid)); + if (accounts == null || accounts.isEmpty()) { + log.warn(LoggerHelper.buildInfoPrivateRequest(requestId, "Se intento guardar una lista de cuentas vacia o nula. No se realiza la operacion.")); + return; + } + + try { + userAccountRedisRepository.saveOrUpdate(sid, accounts); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Guardado exitoso en Redis para el sid", sid)); + } catch (Exception ex) { + log.error(LoggerHelper.buildError(requestId, String.format("Error al intentar guardar en Redis para el sid %s. %s", sid, ex.getMessage()))); + } + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/service/OtpValidationService.java b/bus-evaluate-customer-product/src/main/java/application/service/OtpValidationService.java new file mode 100644 index 0000000..b13002a --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/service/OtpValidationService.java @@ -0,0 +1,67 @@ +package application.service; + +import application.port.input.IValidateOtp; +import application.port.input.command.ValidateOtpCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import infrastructure.config.DomainConfig; +import infrastructure.config.OtpConfig; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ApplicationScoped +public class OtpValidationService { + + private final IValidateOtp validateOtpUseCase; + private final OtpConfig otpConfig; + private final DomainConfig domainConfig; + + @Inject + public OtpValidationService( + IValidateOtp validateOtpUseCase, + OtpConfig otpConfig, + DomainConfig domainConfig + ) { + this.validateOtpUseCase = validateOtpUseCase; + this.otpConfig = otpConfig; + this.domainConfig = domainConfig; + } + + /** + * Punto de entrada principal para validar un OTP. + * + * @return {@code true} si el OTP es válido, de lo contrario {@code false}. + */ + public boolean isOtpValid( + String otpToken, + String originApp, + String login, + String ipAddress, + String requestId + ) { + return validateOtpFromApiService(otpToken, originApp, login, ipAddress, requestId); + } + + private boolean validateOtpFromApiService( + String otpToken, + String originApp, + String login, + String ipAddress, + String requestId + ) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Validando OTP usando servicio externo")); + + var otpCommand = new ValidateOtpCommand( + otpToken, + originApp, + login, + ipAddress, + otpConfig.channel(), + otpConfig.servicio(), + domainConfig.payment() + ); + + return validateOtpUseCase.isValidOtp(otpCommand, requestId); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/service/PostgresConfigService.java b/bus-evaluate-customer-product/src/main/java/application/service/PostgresConfigService.java new file mode 100644 index 0000000..6d2fedb --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/service/PostgresConfigService.java @@ -0,0 +1,63 @@ +package application.service; + +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import com.banesco.common.infraestructure.repository.PostgresRedisRepository; +import domain.entity.AppConfiguration; +import infrastructure.mapper.PostgresMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@Slf4j +@ApplicationScoped +public class PostgresConfigService { + + @Inject + PostgresRedisRepository postgresRedisRepository; + + /** + * Recibe una entidad AppConfiguration de la lógica de negocio y la mapea + * a un CustomerAccountPostgresConfig del "commons" para guardarla en Redis. + */ + public void savePostgresConfig(String configName, AppConfiguration appConfigItem, long ttlInSeconds, String requestId) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando guardado de configuracion postgres en Redis", appConfigItem.toString())); + + try { + var commonConfigToSave = PostgresMapper.mapToCommon(appConfigItem); + postgresRedisRepository.save(configName, commonConfigToSave, ttlInSeconds); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Guardado exitoso en Redis. Clave: " + configName)); + } catch (Exception ex) { + log.error(LoggerHelper.buildError(requestId, "Error al intentar guardar configuracion de postgres en Redis"), ex); + } + } + + /** + * Busca una configuración en Redis por su nombre (clave). + * + * @param configName La clave de Redis (ej: el appId). + * @param requestId El ID de la traza para logging. + * @return Un Optional con la entidad AppConfiguration si se encuentra en caché. + */ + public Optional findByName(String configName, String requestId) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Buscando configuracion en Redis. Clave: " + configName)); + try { + var commonConfigOpt = postgresRedisRepository.findByName(configName); + + if (commonConfigOpt.isEmpty()) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Cache miss para clave: " + configName)); + return Optional.empty(); + } + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Cache hit para clave: " + configName)); + + return Optional.of(PostgresMapper.mapToApp(commonConfigOpt.get())); + + } catch (Exception ex) { + log.error(LoggerHelper.buildError(requestId, "Error al intentar leer configuracion de postgres en Redis"), ex); + // Si hay error en Redis, es mejor tratarlo como un "cache miss" (no encontrado) + return Optional.empty(); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java new file mode 100644 index 0000000..20b0acb --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java @@ -0,0 +1,84 @@ +package application.usecase; + +import application.exception.GatewayServiceException; +import application.port.input.ICustomerProduct; +import application.port.input.command.CustomerAccountCommand; +import application.service.AccountService; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.entity.AppConfiguration; +import domain.model.AccountClientContext; +import domain.model.BankingProduct; +import domain.repository.ICustomerProductPort; +import infrastructure.mapper.BankingProductMapper; +import infrastructure.mapper.CacheMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.List; + +@Slf4j +@ApplicationScoped +public class CustomerProductUseCase implements ICustomerProduct { + + private final ICustomerProductPort customerProductPort; + private final AccountService accountService; + + @ConfigProperty(name = "app.redis.expiration") + long cacheTtl; + + @Inject + public CustomerProductUseCase( + ICustomerProductPort customerProductPort, + AccountService accountService + ) { + this.customerProductPort = customerProductPort; + this.accountService = accountService; + } + + @Override + public List execute(CustomerAccountCommand command, AccountClientContext context, AppConfiguration appConfig, String otp, Boolean isCacheable, String requestId) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: api-get-db2-accounts-client-v2")); + try { + + var customerAccountsFromService = customerProductPort.execute(command, requestId); + + // Si el servicio no devuelve nada, no hay nada que guardar ni devolver. + if (customerAccountsFromService == null || customerAccountsFromService.isEmpty()) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio principal no devolvio cuentas para requestId {}", requestId)); + return List.of(); + } + + var bankingProducts = BankingProductMapper.fromCustomerAccountList(customerAccountsFromService, appConfig.isEncryptionData()); + + if(isCacheable) { + var accountsToCache = CacheMapper.toUserAccountRedisList( + customerAccountsFromService, + context.getRedisKey(), + context.getFinalCustomerId(), + otp + ); + + log.info(LoggerHelper.buildInfoPrivateRequest( + requestId, String.format("Guardando %s cuentas en cache", accountsToCache.size()) + )); + + accountService.saveAccounts( + requestId, + context.getFinalCustomerId(), + context.getRedisKey(), + accountsToCache, + cacheTtl + ); + } + return bankingProducts; + } catch (GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de api-get-db2-accounts-client-v2")); + throw ex; + } finally { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: api-get-db2-accounts-client-v2")); + } + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/EncodeValueUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/EncodeValueUseCase.java new file mode 100644 index 0000000..b4fc9cf --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/usecase/EncodeValueUseCase.java @@ -0,0 +1,36 @@ +package application.usecase; + +import application.exception.GatewayServiceException; +import application.port.input.IEncodeValue; +import application.port.input.command.EncodeValueCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.repository.IEncodeValuePort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ApplicationScoped +public class EncodeValueUseCase implements IEncodeValue { + + private final IEncodeValuePort encodeValuePort; + + @Inject + public EncodeValueUseCase(IEncodeValuePort encodeValuePort) { + this.encodeValuePort = encodeValuePort; + } + + @Override + public String execute(EncodeValueCommand command, String requestId) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Encode Value")); + try { + return encodeValuePort.execute(command, requestId); + } catch (GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de Encode Value: " + ex.getMessage())); + throw ex; + } finally { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Encode Value")); + } + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/GetAppConfigUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/GetAppConfigUseCase.java new file mode 100644 index 0000000..f7e06a6 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/usecase/GetAppConfigUseCase.java @@ -0,0 +1,28 @@ +package application.usecase; + +import application.port.input.IAppConfig; +import domain.entity.AppConfiguration; +import domain.repository.AppConfigRepository; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ApplicationScoped +public class GetAppConfigUseCase implements IAppConfig { + + private final AppConfigRepository appConfigRepository; + + @Inject + public GetAppConfigUseCase(AppConfigRepository appConfigRepository) { + this.appConfigRepository = appConfigRepository; + } + + public AppConfiguration execute(String appName) { + + return appConfigRepository.findConfigByAppName(appName) + .orElseThrow(() -> new NotFoundException("Configuracion no encontrada para la app: " + appName)); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/ServiceStatusUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/ServiceStatusUseCase.java new file mode 100644 index 0000000..6d6e592 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/usecase/ServiceStatusUseCase.java @@ -0,0 +1,72 @@ +package application.usecase; + +import application.exception.GatewayServiceException; +import application.port.input.IServiceStatus; +import application.port.input.command.ServiceStatusCommand; +import application.port.output.IMaintenanceCheckPort; +import com.banesco.common.infraestructure.helpers.JsonHelper; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.repository.IStatusPort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +@ApplicationScoped +public class ServiceStatusUseCase implements IServiceStatus { + + private static final String INACTIVE_STATUS = "I"; + + private final IStatusPort statusApiPort; + private final IMaintenanceCheckPort maintenancePort; + + @Inject + public ServiceStatusUseCase( + IStatusPort statusApiService, + IMaintenanceCheckPort maintenancePort + ) { + this.statusApiPort = statusApiService; + this.maintenancePort = maintenancePort; + } + + /** + * Determina el estado de disponibilidad de un servicio, devolviendo true si está activo y false si no lo está. + * + * @param command El objeto de comando que contiene los datos necesarios para la consulta, como el ID de aplicación y transacción. + * @param requestId Un identificador único de la solicitud, utilizado principalmente para la trazabilidad en los logs. + * @return {@code true} si el servicio se considera activo; {@code false} si está inactivo, en mantenimiento o si la verificación falló. + */ + @Override + public Boolean execute(ServiceStatusCommand command, String requestId) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Service Status")); + + // Si no estamos en ventana de mantenimiento, el servicio está activo. + if(!maintenancePort.isUnderMaintenance(requestId)) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "No estamos en ventana de mantenimiento. Servicio ACTIVO")); + return true; + } + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "En ventana de mantenimiento, consultando estado remoto...")); + + try { + // Si estamos en horario de validacion, consultamos el estado remoto. + var remoteStatus = statusApiPort.getServiceStatus(command, requestId); + + var isActive = remoteStatus != null && !INACTIVE_STATUS.equalsIgnoreCase(remoteStatus.getStatus()); + + if (isActive) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio externo confirmo estado ACTIVO")); + } else { + log.warn(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio externo esta nulo o en estado INACTIVO")); + } + return isActive; + } catch (GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de service status")); + throw ex; + } finally { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Service Status")); + } + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/UserLoginUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/UserLoginUseCase.java new file mode 100644 index 0000000..f6131f6 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/usecase/UserLoginUseCase.java @@ -0,0 +1,52 @@ +package application.usecase; + +import application.exception.GatewayServiceException; +import application.port.input.IUserLogin; +import application.port.input.command.UserLoginCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.model.UserLogin; +import domain.repository.IUserLoginPort; +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@Slf4j +@ApplicationScoped +public class UserLoginUseCase implements IUserLogin { + + private final IUserLoginPort userLoginApiPort; + + public UserLoginUseCase(IUserLoginPort userLoginApiPort) { + this.userLoginApiPort = userLoginApiPort; + } + + /** + * Orquesta la ejecución del caso de uso de login de usuario + * Encapsula la lógica de negocio, incluyendo la generación de códigos de referencia + * y el manejo de fallos de comunicación con el servicio externo. + * @param command El comando con los datos de entrada para el login. + * @param requestId El ID de la solicitud para trazabilidad. + * @return Un {@link Optional} con el objeto {@link UserLogin} si el login fue exitoso, + * o un {@code Optional.empty()} si falló por cualquier motivo. + */ + @Override + public Optional execute(UserLoginCommand command, String requestId) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: User Login")); + try { + var result = userLoginApiPort.execute(command, requestId); + + if(result == null) { + log.error(LoggerHelper.buildError(requestId, " El login no fue exitoso segun la respuesta del servicio")); + return Optional.empty(); + } + + log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: User Login")); + return Optional.of(result); + } catch (GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de User Login")); + throw ex; + } + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateOtpUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateOtpUseCase.java new file mode 100644 index 0000000..90c00ad --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateOtpUseCase.java @@ -0,0 +1,57 @@ +package application.usecase; + +import application.exception.GatewayServiceException; +import application.exception.InvalidTokenException; +import application.port.input.IValidateOtp; +import application.port.input.command.ValidateOtpCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.repository.IOtpPort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import org.jboss.resteasy.reactive.RestResponse; + +@Slf4j +@ApplicationScoped +public class ValidateOtpUseCase implements IValidateOtp { + + private final IOtpPort otpApiPort; + + @Inject + public ValidateOtpUseCase(IOtpPort otpApiService) { + this.otpApiPort = otpApiService; + } + + /** + * Orquesta la validación de un token OTP. + * Encapsula la lógica de negocio para determinar si un OTP es válido, + * manejando también los fallos de comunicación con el servicio externo. + * + * @param command El comando con los datos necesarios para validar el OTP. + * @param requestId El ID de la solicitud para trazabilidad. + * @return {@code true} si el servicio externo responde con un status HTTP 200 (OK), + * {@code false} en cualquier otro caso (respuesta de error o fallo de comunicación). + */ + @Override + public Boolean isValidOtp(ValidateOtpCommand command, String requestId) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Validate OTP")); + + try { + var apiResponse = otpApiPort.validateOtp(command, requestId); + var isValid = apiResponse.getStatus() == RestResponse.Status.OK.getStatusCode(); + + log.info("Validacion de OTP para requestId {}. Resultado: {}. Codigo de estado recibido: {}", + requestId, isValid, apiResponse.getStatus()); + + return isValid; + } catch(InvalidTokenException ex) { + return false; + } catch(GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de validate otp")); + throw ex; + } finally { + log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: Validate OTP")); + } + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateProfileUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateProfileUseCase.java new file mode 100644 index 0000000..39b3c47 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateProfileUseCase.java @@ -0,0 +1,81 @@ +package application.usecase; + +import application.exception.GatewayServiceException; +import application.port.input.IValidateProfile; +import application.port.input.command.ValidateProfileCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.model.ClientProfile; +import domain.repository.IValidateProfilePort; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@ApplicationScoped +public class ValidateProfileUseCase implements IValidateProfile { + + private final IValidateProfilePort validateProfilePort; + + @Inject + public ValidateProfileUseCase(IValidateProfilePort validateProfilePort) { + this.validateProfilePort = validateProfilePort; + } + + /** + * Orquesta la validación del perfil de un cliente. + * Determina si un perfil es válido según la respuesta de un servicio externo, + * navegando de forma segura a través de una estructura de datos anidada. + * + * @param command El comando con los datos de entrada para la validación. + * @param requestId El ID de la solicitud para trazabilidad. + * @return {@code true} si el perfil es válido (status = 0), {@code false} en cualquier otro caso. + */ + @Override + public Boolean execute(ValidateProfileCommand command, String requestId) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Validate Profile")); + try { + var result = validateProfilePort.execute(command, requestId); + + // La validación completa se realiza en una única cadena funcional y segura. + var isValidProfile = Optional.ofNullable(result) + .filter(profile -> !profile.getHasError()) + .flatMap(this::extractStatusFromData) + .map(status -> status == 0) + .orElse(false); + + log.info("{} - Validacion de perfil completada ¿Es valido?: {}", requestId, isValidProfile); + return isValidProfile; + + } catch (GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de Validate Profile")); + throw ex; + } finally { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Validate Profile")); + } + } + + /** + * Helper privado para navegar de forma segura la estructura de Mapas anidada + * y extraer el valor de 'stausu'. + * + * @param profile El objeto ClientProfile que contiene los datos. + * @return Un Optional con el valor de 'stausu' si se encuentra. + */ + @SuppressWarnings("unchecked") + private Optional extractStatusFromData(ClientProfile profile) { + return Optional.ofNullable(profile.getData()) + .filter(Map.class::isInstance).map(m -> (Map) m) + .map(data -> data.get("banesco")) + .filter(Map.class::isInstance).map(m -> (Map) m) + .map(banesco -> banesco.get("respuesta")) + .filter(Map.class::isInstance).map(m -> (Map) m) + .map(res -> res.get("respuesta")) + .filter(Map.class::isInstance).map(m -> (Map) m) + .map(respuesta -> respuesta.get("stausu")) + .filter(Integer.class::isInstance) + .map(Integer.class::cast); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java b/bus-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java new file mode 100644 index 0000000..bcefbc2 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java @@ -0,0 +1,60 @@ +package domain.dto.request; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@RegisterForReflection +public class CustomerRequest { + + @Valid + @NotNull(message = "VDE01") + private CustomerPositionState customerPositionState; + + @Valid + @NotNull(message = "VDE01") + private Device device; + + private TokenAssignmentInstance tokenAssignmentInstance; + + @Data + public static class CustomerPositionState { + private CustomerReference customerReference; + + @Data + public static class CustomerReference { + + /** + * Valida que sea una única letra del conjunto [V, E, P, G, J]. + */ + private String customerIdType; + + /** + * Valida que sea numérico con un máximo de 9 dígitos. + */ + private String customerId; + + } + + } + + @Data + public static class Device { + private String deviceType; + private String deviceDescription; + private String deviceIp; + private String deviceSessionReference; + } + + @Data + public static class TokenAssignmentInstance { + private String tokenIdentificationCode; + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java new file mode 100644 index 0000000..4a1a529 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java @@ -0,0 +1,16 @@ +package domain.dto.response; + +import domain.model.BankingProduct; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Value +@Builder +@RegisterForReflection +public class CustomerPositionStateResponse { + int record; + List bankingProducts; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java new file mode 100644 index 0000000..70840e2 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java @@ -0,0 +1,23 @@ +package domain.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Getter; + +@Getter +@RegisterForReflection +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CustomerResponse { + StatusResponse statusResponse; + CustomerPositionStateResponse customerPositionState; + + public CustomerResponse(StatusResponse statusResponse, CustomerPositionStateResponse customerPositionState) { + this.statusResponse = statusResponse; + this.customerPositionState = customerPositionState; + } + + public CustomerResponse(StatusResponse statusResponse) { + this.statusResponse = statusResponse; + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java b/bus-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java new file mode 100644 index 0000000..362b096 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java @@ -0,0 +1,15 @@ +package domain.dto.response; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class StatusResponse { + String status; + String statusCode; + String message; + String traceId; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/entity/AppConfiguration.java b/bus-evaluate-customer-product/src/main/java/domain/entity/AppConfiguration.java new file mode 100644 index 0000000..8734bb8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/entity/AppConfiguration.java @@ -0,0 +1,31 @@ +package domain.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import domain.model.Configuration; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@RegisterForReflection +public class AppConfiguration { + + @JsonIgnore + private Long appId; + + private String appName; + + private String apiName; + + private String type; + + private String event; + + private boolean encryptionData; + + private Configuration configuration; + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/entity/ConfigItem.java b/bus-evaluate-customer-product/src/main/java/domain/entity/ConfigItem.java new file mode 100644 index 0000000..dc49715 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/entity/ConfigItem.java @@ -0,0 +1,33 @@ +package domain.entity; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@RegisterForReflection +public class ConfigItem { + boolean cacheable; + String bankCode; + String currencyCode; + String status; + String statusMe; + String productCv; + String product; + String channel; + String service; + String operator; + String phone; + String affiliationStatus; + String counterpartyAccount; + String holder; + String codProduct; + String statusOperation; + String statusRestriction; + String statusOperationRestriction; + + public ConfigItem() {} +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/AccountClientContext.java b/bus-evaluate-customer-product/src/main/java/domain/model/AccountClientContext.java new file mode 100644 index 0000000..5d4ad68 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/AccountClientContext.java @@ -0,0 +1,41 @@ +package domain.model; + +import domain.dto.request.CustomerRequest; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Data; + +import java.util.Optional; + +@Data +@RegisterForReflection +public class AccountClientContext { + // Datos de entrada + final long startExecution; + final String requestId; + final CustomerRequest request; + final String appId, fintechId, finalCustomerId, redisKey; + String username; + + Optional finalError = Optional.empty(); + + public AccountClientContext(long startExecution, String requestId, CustomerRequest request, String... headers) { + var customer = request.getCustomerPositionState().getCustomerReference(); + var sid = request.getDevice().getDeviceSessionReference(); + + this.startExecution = startExecution; + this.requestId = requestId; + this.request = request; + + this.appId = headers[0]; + this.fintechId = headers[1]; + this.finalCustomerId = customer.getCustomerIdType().concat(customer.getCustomerId()); + + this.username = Optional.ofNullable(headers[2]).orElse(this.finalCustomerId); + + this.redisKey = Optional.ofNullable(sid) + .filter(s -> !s.isBlank()) + .map(s -> this.finalCustomerId + sid) + .orElse(this.finalCustomerId); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/BankService.java b/bus-evaluate-customer-product/src/main/java/domain/model/BankService.java new file mode 100644 index 0000000..2c2f4fa --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/BankService.java @@ -0,0 +1,10 @@ +package domain.model; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +public record BankService( + String bankCode, + String serviceCode, + String eventCode +) {} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java b/bus-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java new file mode 100644 index 0000000..8c6ff25 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java @@ -0,0 +1,17 @@ +package domain.model; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; + +@Value +@Builder +@RegisterForReflection +public class BankingProduct { + String productNumber; + String productType; + BigDecimal balance; + String currency; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/ClientProfile.java b/bus-evaluate-customer-product/src/main/java/domain/model/ClientProfile.java new file mode 100644 index 0000000..4a79d99 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/ClientProfile.java @@ -0,0 +1,15 @@ +package domain.model; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class ClientProfile { + Boolean hasError; + String errorMessage; + String errorCode; + Object data; +} diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/Configuration.java b/bus-evaluate-customer-product/src/main/java/domain/model/Configuration.java new file mode 100644 index 0000000..f2a421f --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/Configuration.java @@ -0,0 +1,19 @@ +package domain.model; + +import domain.entity.ConfigItem; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.Map; + +@Getter +@Setter +@ToString +@RegisterForReflection +public class Configuration { + private Map validationRules; + private Map aliases; + private Map configuration; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/CustomerAccount.java b/bus-evaluate-customer-product/src/main/java/domain/model/CustomerAccount.java new file mode 100644 index 0000000..4e029a9 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/CustomerAccount.java @@ -0,0 +1,36 @@ +package domain.model; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; + +@Value +@Builder +@RegisterForReflection +public class CustomerAccount { + String banco; + String cuenta; + String producto; + String descProd; + String claseCta; + String tipoCta; + String moneda; + BigDecimal saldoDisponible; + String estatus; + String statusMe; + String titular; + BigDecimal maximumPerDay; + BigDecimal maximumPerTransaction; + BigDecimal maximumPerMonth; + BigDecimal maximumMonthlyFee; + BigDecimal minimumPerTransaction; + String operator; + String phone; + String channel; + String service; + String affiliationStatus; + String codeError; + String descError; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java b/bus-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java new file mode 100644 index 0000000..76bc51c --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java @@ -0,0 +1,28 @@ +package domain.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.quarkus.runtime.annotations.RegisterForReflection; + +/** + * Representa un código de error de la aplicación. + */ +@RegisterForReflection +public record ErrorCode( + @JsonProperty("backend_code") String backendCode, + @JsonProperty("http_code") String httpCode, + @JsonProperty("status_code") String statusCode, + @JsonProperty("description") String description, + @JsonProperty("status") String status +) { + /** + * Convierte de forma segura el 'httpCode' a un código de estado HTTP numérico. + * @return El código de estado HTTP como un entero. + */ + public int getHttpStatusCode() { + try { + return Integer.parseInt(this.httpCode); + } catch (NumberFormatException e) { + return 500; + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/FoundAccountInfo.java b/bus-evaluate-customer-product/src/main/java/domain/model/FoundAccountInfo.java new file mode 100644 index 0000000..45be319 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/FoundAccountInfo.java @@ -0,0 +1,11 @@ +package domain.model; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +public record FoundAccountInfo( + String clearAccount, + String username, + String otp, + String accountType +) {} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/Otp.java b/bus-evaluate-customer-product/src/main/java/domain/model/Otp.java new file mode 100644 index 0000000..566a2d6 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/Otp.java @@ -0,0 +1,15 @@ +package domain.model; + +import com.banesco.xmlns.enterpriseobjects.msgrshdr.MsgRsHdr; +import infrastructure.client.otp.dto.response.Status; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class Otp { + MsgRsHdr msgRsHdr; + Status status; +} diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/ServiceStatus.java b/bus-evaluate-customer-product/src/main/java/domain/model/ServiceStatus.java new file mode 100644 index 0000000..e1ec746 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/ServiceStatus.java @@ -0,0 +1,12 @@ +package domain.model; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class ServiceStatus { + String status; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/UserLogin.java b/bus-evaluate-customer-product/src/main/java/domain/model/UserLogin.java new file mode 100644 index 0000000..ceb4874 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/model/UserLogin.java @@ -0,0 +1,14 @@ +package domain.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; + +@Builder +@RegisterForReflection +@JsonInclude(JsonInclude.Include.NON_NULL) +public record UserLogin( + String usernameBol, + String usernameBOLE, + String idNumber +) { } \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/AppConfigRepository.java b/bus-evaluate-customer-product/src/main/java/domain/repository/AppConfigRepository.java new file mode 100644 index 0000000..70f1524 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/AppConfigRepository.java @@ -0,0 +1,9 @@ +package domain.repository; + +import domain.entity.AppConfiguration; + +import java.util.Optional; + +public interface AppConfigRepository { + Optional findConfigByAppName(String appName); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/ICustomerProductPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/ICustomerProductPort.java new file mode 100644 index 0000000..19e66b7 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/ICustomerProductPort.java @@ -0,0 +1,10 @@ +package domain.repository; + +import application.port.input.command.CustomerAccountCommand; +import domain.model.CustomerAccount; + +import java.util.List; + +public interface ICustomerProductPort { + List execute(CustomerAccountCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IEncodeValuePort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IEncodeValuePort.java new file mode 100644 index 0000000..ee9a050 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IEncodeValuePort.java @@ -0,0 +1,7 @@ +package domain.repository; + +import application.port.input.command.EncodeValueCommand; + +public interface IEncodeValuePort { + String execute(EncodeValueCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IOtpPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IOtpPort.java new file mode 100644 index 0000000..9659554 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IOtpPort.java @@ -0,0 +1,8 @@ +package domain.repository; + +import application.port.input.command.ValidateOtpCommand; +import org.jboss.resteasy.reactive.RestResponse; + +public interface IOtpPort { + RestResponse validateOtp(ValidateOtpCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IRegisterSecurityPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IRegisterSecurityPort.java new file mode 100644 index 0000000..efc08a8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IRegisterSecurityPort.java @@ -0,0 +1,7 @@ +package domain.repository; + +import application.port.input.command.RegisterSecurityCommand; + +public interface IRegisterSecurityPort { + void writeTrace(RegisterSecurityCommand request, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IStatusPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IStatusPort.java new file mode 100644 index 0000000..b0dcca0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IStatusPort.java @@ -0,0 +1,8 @@ +package domain.repository; + +import application.port.input.command.ServiceStatusCommand; +import domain.model.ServiceStatus; + +public interface IStatusPort { + ServiceStatus getServiceStatus(ServiceStatusCommand query, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IUserLoginPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IUserLoginPort.java new file mode 100644 index 0000000..b66c50b --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IUserLoginPort.java @@ -0,0 +1,11 @@ +package domain.repository; + +import application.port.input.command.UserLoginCommand; +import domain.model.UserLogin; + +public interface IUserLoginPort { + UserLogin execute( + UserLoginCommand command, + String requestId + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IValidateProfilePort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IValidateProfilePort.java new file mode 100644 index 0000000..f513f4d --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IValidateProfilePort.java @@ -0,0 +1,8 @@ +package domain.repository; + +import application.port.input.command.ValidateProfileCommand; +import domain.model.ClientProfile; + +public interface IValidateProfilePort { + ClientProfile execute(ValidateProfileCommand command, String requestId); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/AppConfigResource.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/AppConfigResource.java new file mode 100644 index 0000000..a5ef84d --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/AppConfigResource.java @@ -0,0 +1,111 @@ +package infrastructure.adapter.input.rest; + +import application.helper.ResponseHelper; +import application.port.input.IAppConfig; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.dto.response.CustomerResponse; +import domain.entity.AppConfiguration; +import infrastructure.context.TraceIdContext; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.ExampleObject; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.jboss.resteasy.reactive.RestPath; + +@Slf4j +@Path("/customer") +@Produces(MediaType.APPLICATION_JSON) +public class AppConfigResource { + + // --- CASOS DE USO Y SERVICIOS --- + private final IAppConfig appConfigUseCase; + + // --- DEPENDENCIAS ADICIONALES --- + private final ResponseHelper responseHelper; + private final TraceIdContext traceIdContext; + + public AppConfigResource( + IAppConfig appConfigUseCase, + ResponseHelper responseHelper, + TraceIdContext traceIdContext, + StatusDescriptionResolver statusDescriptionResolver + ) { + this.appConfigUseCase = appConfigUseCase; + this.responseHelper = responseHelper; + this.traceIdContext = traceIdContext; + } + + @GET + @Path("/config/{appName}") + @Operation( + summary = "Obtener Configuración de Aplicación", + description = "Obtiene la configuración JSON completa para un 'appName' (appId) específico. Este endpoint se usa para testing o recarga manual." + ) + @Parameter( + name = "appName", + in = ParameterIn.PATH, + description = "El 'appName' (appId) que se usará para buscar la configuración.", + required = true, + schema = @Schema(type = SchemaType.STRING), + example = "abc123" + ) + @APIResponses(value = { + @APIResponse( + responseCode = "200", + description = "Configuración encontrada.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = AppConfiguration.class), + examples = { + @ExampleObject( + name = "Configuración encontrada", + externalValue = "/openapi-examples/response/app-config.json" + ) + } + ) + ), + @APIResponse( + responseCode = "400", + description = "Configuración no encontrada. El 'appName' no existe en la base de datos.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CustomerResponse.class), + examples = { + @ExampleObject( + name = "Configuración no encontrada", + summary = "VRN_CONFIG_NOT_FOUND", + externalValue = "/openapi-examples/response/config-not-found.json" + ) + } + ) + ) + }) + public Response getConfig(@RestPath String appName) { + final var requestId = traceIdContext.getTraceId(); + + try { + var config = appConfigUseCase.execute(appName); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada para appName: " + appName)); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion: " + config)); + + return Response.ok(config).build(); + + } catch (NotFoundException ex) { + log.warn(LoggerHelper.buildError(requestId, "Configuracion no encontrada (VRN_CONFIG_NOT_FOUND) para appName: " + appName)); + return responseHelper.buildErrorResponse("VRN_CONFIG_NOT_FOUND", requestId); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java new file mode 100644 index 0000000..fef9312 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java @@ -0,0 +1,540 @@ +package infrastructure.adapter.input.rest; + +import application.factory.SecurityTraceFactory; +import application.helper.ResponseHelper; +import application.port.input.*; +import application.port.input.command.*; +import application.service.OtpValidationService; +import application.service.PostgresConfigService; +import com.banesco.common.infraestructure.helpers.JsonHelper; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.dto.request.CustomerRequest; +import domain.dto.response.CustomerResponse; +import domain.dto.response.StatusResponse; +import domain.entity.AppConfiguration; +import domain.entity.ConfigItem; +import domain.model.AccountClientContext; +import domain.model.BankService; +import domain.model.ErrorCode; +import domain.model.UserLogin; +import infrastructure.config.AvailabilityBolConfig; +import infrastructure.context.TraceIdContext; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.ExampleObject; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameters; +import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Path("/customer") +public class CustomerResource { + + @ConfigProperty(name = "app.redis.expiration") + long cacheTtl; + + // --- DEPENDENCIAS INYECTADAS --- + private final TraceIdContext traceIdContext; + private final ResponseHelper responseHelper; + private final SecurityTraceFactory securityTraceFactory; + private final StatusDescriptionResolver statusDescriptionResolver; + + // --- CONFIGURACIONES --- + private final AvailabilityBolConfig bolConfig; + + // --- CASOS DE USO Y SERVICIOS --- + private final IAppConfig appConfigUseCase; + private final IServiceStatus serviceStatusUseCase; + private final IUserLogin userLoginUseCase; + private final IValidateProfile validateProfileUseCase; + private final IEncodeValue encodeValueUseCase; + private final ICustomerProduct customerProductUseCase; + private final IRegisterSecurity registerSecurityUseCase; + private final OtpValidationService otpValidationService; + private final PostgresConfigService postgresConfigService; + + public CustomerResource( + TraceIdContext traceIdContext, + ResponseHelper responseHelper, + SecurityTraceFactory securityTraceFactory, + AvailabilityBolConfig bolConfig, + StatusDescriptionResolver statusDescriptionResolver, + IAppConfig appConfigUseCase, + IServiceStatus serviceStatusUseCase, + IUserLogin userLoginUseCase, + IValidateProfile validateProfileUseCase, + IEncodeValue encodeValueUseCase, + ICustomerProduct customerProductUseCase, + IRegisterSecurity registerSecurityUseCase, + OtpValidationService otpValidationService, + PostgresConfigService postgresConfigService + ) { + this.traceIdContext = traceIdContext; + this.responseHelper = responseHelper; + this.securityTraceFactory = securityTraceFactory; + this.bolConfig = bolConfig; + this.statusDescriptionResolver = statusDescriptionResolver; + this.appConfigUseCase = appConfigUseCase; + this.serviceStatusUseCase = serviceStatusUseCase; + this.userLoginUseCase = userLoginUseCase; + this.validateProfileUseCase = validateProfileUseCase; + this.encodeValueUseCase = encodeValueUseCase; + this.customerProductUseCase = customerProductUseCase; + this.registerSecurityUseCase = registerSecurityUseCase; + this.otpValidationService = otpValidationService; + this.postgresConfigService = postgresConfigService; + } + + @POST + @Path("/position/evaluate") + @Operation( + summary = "Consulta de cuentas", + description = "Orquesta el flujo de validaciones (estatus de servicio, perfil, OTP, etc.) y, si son exitosas, consulta los productos del cliente." + ) + @Parameters({ + @Parameter( + name = "requestId", + in = ParameterIn.HEADER, + description = "Identificador de la solicitud para la trazabilidad. Si se omite, se genera uno automático.", + schema = @Schema(type = SchemaType.STRING), + example = "7WCnvznd0CPrBZ0jF4af" + ), + @Parameter( + name = "appId", + in = ParameterIn.HEADER, + description = "Identificador de la aplicación cliente para cargar una configuración de header específica. Si se omite, se usa la configuración por defecto.", + schema = @Schema(type = SchemaType.STRING), + example = "abc123" + ) + }) + @RequestBody( + description = "Cuerpo de la solicitud con los datos necesarios para consultar las cuentas.", + content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = + @Schema(implementation = CustomerRequest.class), + examples = { + @ExampleObject( + name = "Solicitud Estándar", + summary = "Ejemplo de solicitud con éxito", + externalValue = "/openapi-examples/request/customer-product-success.json" + ), + @ExampleObject( + name = "Solicitud con Token", + summary = "Ejemplo de solicitud con token (éxito)", + externalValue = "/openapi-examples/request/customer-product-token-success.json" + ) + } + ) + ) + @APIResponses(value = { + @APIResponse( + responseCode = "200", + description = "Operación Exitosa. Devuelve la lista de productos del cliente.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CustomerResponse.class), + examples = { + @ExampleObject( + name = "Cuentas Enmascaradas", + summary = "Respuesta exitosa (Enmascaramiento activo)", + description = "La configuración de la app indica que 'encryptionData' es true.", + externalValue = "/openapi-examples/response/customer-product-response.json" + ), + @ExampleObject( + name = "Cuentas Reales", + summary = "Respuesta exitosa (Sin enmascaramiento)", + description = "La configuración de la app indica que 'encryptionData' es false.", + externalValue = "/openapi-examples/response/customer-product-response-unmasked.json" + ), + @ExampleObject( + name = "Sin Resultados", + summary = "Respuesta exitosa (Cliente sin productos)", + description = "La consulta se completó, pero el cliente no tiene productos que coincidan.", + externalValue = "/openapi-examples/response/customer-product-without-results.json" + ) + } + ) + ), + @APIResponse( + responseCode = "401", + description = "Error de Autorización o Perfil. El perfil del cliente no es válido o falló una validación de seguridad.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Perfil Inválido", + summary = "VRN01 - Perfil no válido", + externalValue = "/openapi-examples/response/invalid-profile.json" + ) + } + ) + ), + @APIResponse( + responseCode = "400", + description = "Datos de entrada inválidos, como un token OTP incorrecto o una configuración de app no encontrada.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Token Inválido", + summary = "VRN07 - OTP Inválido", + externalValue = "/openapi-examples/response/invalid-token.json" + ), + @ExampleObject( + name = "Configuración no encontrada", + summary = "VRN_CONFIG_NOT_FOUND", + externalValue = "/openapi-examples/response/config-not-found.json" + ) + } + ) + ), + @APIResponse( + responseCode = "404", + description = "Recurso no encontrado. Puede ser un usuario BOL, un login vacío.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CustomerResponse.class), + examples = { + @ExampleObject( + name = "Usuario no existe", + summary = "AUTH02 - Usuario no existe", + externalValue = "/openapi-examples/response/user-bol-not-exist.json" + ), + @ExampleObject( + name = "Usuario vacío", + summary = "AUTH01 - Login vacío", + externalValue = "/openapi-examples/response/user-not-exist.json" + ), + } + ) + ), + @APIResponse( + responseCode = "409", + description = "CONFLICTO.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Conflicto", + summary = "Conflicto (revisar)", + externalValue = "/openapi-examples/response/conflict.json" + ) + } + ) + ), + @APIResponse( + responseCode = "500", + description = "Uso Interno. Ocurrio un error inesperado.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Uso Interno", + summary = "Uso Interno (revisar)", + externalValue = "/openapi-examples/response/internal-use.json" + ) + } + ) + ), + @APIResponse( + responseCode = "503", + description = "Servicio no disponible. Ocurrió un error con una dependencia interna o el servicio está en mantenimiento.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Mantenimiento", + summary = "VRN04 - Servicio en mantenimiento", + externalValue = "/openapi-examples/response/service-unavailable.json" + ), + @ExampleObject( + name = "Error de Dependencia", + summary = "Error de CI", + externalValue = "/openapi-examples/response/customer-product-rest-client-error.json" + ) + } + ) + ) + }) + public Response getCustomerProducts( + @HeaderParam("appId") String appId, + @HeaderParam("customerReferenceFintechId") String customerReferenceFintechId, + @HeaderParam("customerReferenceUser") String customerReferenceUser, + @Valid @NotNull(message = "VDE01") CustomerRequest request + ) { + final var requestId = traceIdContext.getTraceId(); + final long startExecution = System.currentTimeMillis(); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, JsonHelper.getJsonFromObject(request))); + + var context = new AccountClientContext(startExecution, requestId, request, + appId, customerReferenceFintechId, customerReferenceUser + ); + + var appConfigurationOpt = getPgConfig(context, requestId); + + if (appConfigurationOpt.isEmpty()) { + var error = statusDescriptionResolver.getCode("VRN_CONFIG_NOT_FOUND"); + return responseHelper.buildErrorResponse(error, requestId); + } + + var appConfiguration = appConfigurationOpt.get(); + + var validationRules = appConfiguration.getConfiguration().getValidationRules(); + var validationErrorOpt = runValidationPipeline(context, validationRules, requestId); + + Response finalResponse; + if (validationErrorOpt.isPresent()) { + context.setFinalError(validationErrorOpt); + finalResponse = responseHelper.buildErrorResponse(validationErrorOpt.get(), requestId); + } else { + var configItem = findSpecificConfigItem(appConfiguration, context, requestId); + + if (configItem == null) { + log.error(LoggerHelper.buildError(requestId, "FATAL: No se encontro un ConfigItem (ni especifico ni 'all')")); + var error = statusDescriptionResolver.getCode("VRN_ITEM_NOT_FOUND"); + return responseHelper.buildErrorResponse(error, requestId); + } + + var otp = Optional.ofNullable(request.getTokenAssignmentInstance()) + .map(CustomerRequest.TokenAssignmentInstance::getTokenIdentificationCode) + .filter(otpCode -> !otpCode.isEmpty()) + .orElse(null); + + finalResponse = getUserAccounts(context, appConfiguration, configItem, otp, requestId); + } + + var fullResponse = (CustomerResponse) finalResponse.getEntity(); + + var statusRegister = fullResponse.getStatusResponse(); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Ejecutando tareas finales (traza de seguridad)")); + var securityCommand = securityTraceFactory.createCommandFromContext(context, statusRegister); + registerSecurityUseCase.execute(securityCommand, context.getRequestId()); + + return finalResponse; + } + + /** + * Ejecuta la secuencia de validaciones. Si alguna falla, devuelve la respuesta de error. + * Si todas pasan, devuelve un Optional vacío. + */ + @SuppressWarnings("t") + private Optional runValidationPipeline(AccountClientContext context, Map validationRules, String requestId) { + var data = context.getRequest(); + var device = data.getDevice(); + var customerReference = data.getCustomerPositionState().getCustomerReference(); + var tokenInstance = data.getTokenAssignmentInstance(); + + if (shouldRun(validationRules, "serviceStatus")) { + if (!isServiceActive(context.getRequestId())) { + return Optional.of(statusDescriptionResolver.getCode("VRN04")); + } + } + + // Primero intenta obtener el usuario del contexto + var userOpt = Optional.ofNullable(context.getUsername()) + .filter(u -> !u.isBlank()) + .map(u -> UserLogin.builder() + .usernameBol(u) + .idNumber(customerReference.getCustomerId()) + .build()); + + // Si no está en el contexto Y la regla dice que se debe ejecutar, búscalo en la API + if (shouldRun(validationRules, "getUserLogin")) { + userOpt = getUserLogin(context.getRequestId(), customerReference.getCustomerId(), customerReference.getCustomerIdType()); + + if (userOpt.isEmpty() ) { + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "No se pudo obtener el usuario ni desde el contexto ni desde la API")); + return Optional.of(statusDescriptionResolver.getCode("VRN19")); + } + + // Si viene vacio el usernameBol "" + if(userOpt.get().usernameBol().isBlank()) { + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "No se pudo obtener el usernameBol")); + return Optional.of(statusDescriptionResolver.getCode("VRN20")); + } + + context.setUsername(userOpt.get().usernameBol()); + } + + String hash = ""; // Inicializar + if (shouldRun(validationRules, "encodeValue")) { + hash = encodeUsername(context.getUsername(), requestId); + } + + if (shouldRun(validationRules, "validateOtp")) { + if(!tokenInstance.getTokenIdentificationCode().isEmpty()) { + var isOtpValid = otpValidationService.isOtpValid( + tokenInstance.getTokenIdentificationCode(), + context.getFintechId(), + context.getUsername(), + device.getDeviceIp(), + requestId + ); + if (!isOtpValid) { + return Optional.of(statusDescriptionResolver.getCode("VRN07")); + } + } + } + + if (shouldRun(validationRules, "validateProfile")) { + var command = new ValidateProfileCommand( + device.getDeviceSessionReference() == null || device.getDeviceSessionReference().isBlank() ? context.getFinalCustomerId() + device.getDeviceSessionReference() : context.getFinalCustomerId(), + device.getDeviceIp(), + hash + ); + var isValidProfile = validateProfileUseCase.execute(command, requestId); + + if(!isValidProfile) { + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "FALLO LA VALIDACION DEL CLIENTE EN TRANSACT")); + return Optional.of(statusDescriptionResolver.getCode("VRN01")); + } + } + + return Optional.empty(); + + } + + // --- MÉTODOS PRIVADOS DE ORQUESTACIÓN --- + private boolean isServiceActive(String requestId) { + var bankService = new BankService(bolConfig.codBan(), bolConfig.codEve(), bolConfig.codServ()); + var command = new ServiceStatusCommand(requestId, requestId, bankService); + return serviceStatusUseCase.execute(command, requestId); + } + + private Optional getUserLogin(String requestId, String... params) { + var command = new UserLoginCommand( + params[0], + params[1] + ); + + return userLoginUseCase.execute(command, requestId); + } + + private String encodeUsername(String username, String requestId) { + var command = new EncodeValueCommand(username); + return encodeValueUseCase.execute(command, requestId); + } + + private Optional getPgConfig(AccountClientContext context, String requestId) { + var configKey = context.getAppId(); + + var cachedConfig = postgresConfigService.findByName(configKey, requestId); + if (cachedConfig.isPresent()) { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion obtenida desde CACHE")); + return cachedConfig; + } + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion no encontrada en cache, buscando en BD...")); + + AppConfiguration appConfiguration; + try { + appConfiguration = appConfigUseCase.execute(configKey); + + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Configuracion obtenida desde BD")); + } catch (NotFoundException ex) { + log.error(LoggerHelper.buildError(requestId, "FATAL: No se encontro AppConfiguration para appId: " + configKey), ex); + return Optional.empty(); + } + + postgresConfigService.savePostgresConfig(configKey, appConfiguration, cacheTtl, requestId); + return Optional.of(appConfiguration); + } + + /** + * Extrae el ConfigItem específico de la AppConfiguration basado en el contexto. + * Cae a "all" si no se encuentra uno específico. + */ + private ConfigItem findSpecificConfigItem(AppConfiguration appConfiguration, AccountClientContext context, String requestId) { + var data = context.getRequest().getCustomerPositionState().getCustomerReference(); + var postgresConfig = appConfiguration.getConfiguration(); + + var aliases = postgresConfig.getAliases(); + var configMap = postgresConfig.getConfiguration(); + + // Usamos el appId del contexto como prefijo de la clave + var literal = data.getCustomerIdType(); + var finalLiteral = aliases.getOrDefault(literal, literal); + var specificKey = context.getAppId() + "_" + finalLiteral; + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Buscando ConfigItem con KEY: " + specificKey)); + + ConfigItem config; + if (configMap.containsKey(specificKey)) { + config = configMap.get(specificKey); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada (Especifica): " + specificKey)); + } else { + config = configMap.get("all"); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada (General): all")); + } + + return config; // Puede ser null si "all" tampoco existe + } + + /** + * Helper para verificar de forma segura si una regla debe ejecutarse. + * Por defecto, si una regla no está en el mapa, SÍ se ejecuta (falla seguro). + */ + private boolean shouldRun(Map rules, String ruleName) { + if (rules == null) { + // Si no hay mapa, orquesta completo + return true; + } + return rules.getOrDefault(ruleName, true); + } + + + private Response getUserAccounts(AccountClientContext context, AppConfiguration appConfig, ConfigItem configItem, String otp, String requestId) { + + var customerAccountCommand = CustomerAccountCommand.builder() + .rif(context.getFinalCustomerId()) + .bankCode(configItem.getBankCode()) + .currencyCode(configItem.getCurrencyCode()) + .status(configItem.getStatus()) + .statusMe(configItem.getStatusMe()) + .productCv(configItem.getProductCv()) + .product(configItem.getProduct()) + .channel(configItem.getChannel()) + .service(configItem.getService()) + .operator(configItem.getOperator()) + .phone(configItem.getPhone()) + .affiliationStatus(configItem.getAffiliationStatus()) + .counterpartyAccount(configItem.getCounterpartyAccount()) + .holder(configItem.getHolder()) + .build(); + + var products = customerProductUseCase.execute(customerAccountCommand, context, appConfig, otp, configItem.isCacheable(), requestId); + + return responseHelper.buildSuccessResponse( + products.stream().toList(), + products.isEmpty() ? + statusDescriptionResolver.getCode("204") : + statusDescriptionResolver.getCode("200"), + requestId + ); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/MaintenanceCheckAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/MaintenanceCheckAdapter.java new file mode 100644 index 0000000..ce118c4 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/MaintenanceCheckAdapter.java @@ -0,0 +1,25 @@ +package infrastructure.adapter.output; + +import application.port.output.IMaintenanceCheckPort; +import com.banesco.common.infraestructure.utils.MaintenanceUtil; +import infrastructure.config.AccountAvailabilityConfig; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ApplicationScoped +public class MaintenanceCheckAdapter implements IMaintenanceCheckPort { + + private final AccountAvailabilityConfig availabilityConfig; + + @Inject + MaintenanceCheckAdapter(AccountAvailabilityConfig paymentAvailabilityConfig) { + this.availabilityConfig = paymentAvailabilityConfig; + } + + @Override + public Boolean isUnderMaintenance(String requestId) { + return MaintenanceUtil.isDateInRange(requestId, availabilityConfig.begin(), availabilityConfig.finish()); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/persistence/AppConfigRepositoryAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/persistence/AppConfigRepositoryAdapter.java new file mode 100644 index 0000000..793c33a --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/persistence/AppConfigRepositoryAdapter.java @@ -0,0 +1,53 @@ +package infrastructure.adapter.output.persistence; + +import com.fasterxml.jackson.databind.ObjectMapper; +import domain.entity.AppConfiguration; +import domain.model.Configuration; +import domain.repository.AppConfigRepository; +import io.agroal.api.AgroalDataSource; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.Optional; + +@ApplicationScoped +public class AppConfigRepositoryAdapter implements AppConfigRepository { + + @Inject + AgroalDataSource dataSource; + + @Inject + ObjectMapper objectMapper; + + @Override + public Optional findConfigByAppName(String appName) { + String sql = "SELECT * FROM get_application_config(?)"; + + try (var conn = dataSource.getConnection(); + var ps = conn.prepareStatement(sql)) { + + ps.setString(1, appName); + + try (var rs = ps.executeQuery()) { + if (rs.next()) { + var config = new AppConfiguration(); + + config.setAppId(rs.getLong("id_appconfig")); + config.setAppName(rs.getString("app_name")); + config.setApiName(rs.getString("api_name")); + config.setType(rs.getString("type")); + config.setEvent(rs.getString("event")); + config.setEncryptionData(rs.getBoolean("encryption_data")); + var jsonString = rs.getString("configuration"); + if (jsonString != null) { + config.setConfiguration(objectMapper.readValue(jsonString, Configuration.class)); + } + + return Optional.of(config); + } + } + } catch (Exception e) { + throw new RuntimeException("Error al llamar a get_application_config", e); + } + return Optional.empty(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java new file mode 100644 index 0000000..6e021bd --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java @@ -0,0 +1,75 @@ +package infrastructure.client.accounts.adapter; + +import application.exception.GatewayServiceException; +import application.port.input.command.CustomerAccountCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.model.CustomerAccount; +import domain.repository.ICustomerProductPort; +import infrastructure.client.accounts.api.CustomerAccountClient; +import infrastructure.mapper.CustomerAccountMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.WebApplicationException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +import java.util.List; + +@Slf4j +@ApplicationScoped +public class CustomerProductAdapter implements ICustomerProductPort { + + private final CustomerAccountClient client; + + public CustomerProductAdapter(@RestClient CustomerAccountClient client) { + this.client = client; + } + + @Override + public List execute(CustomerAccountCommand command, String requestId) { + try { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para api-get-db2-accounts-client-v2")); + var request = CustomerAccountMapper.toRequestDto(command); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de api-get-db2-accounts-client-v2", request)); + + var response = client.readCustomerProducts(requestId, request); + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de api-get-db2-accounts-client-v2", response)); + + // Validación de contrato: asegura que la respuesta no sea nula o inválida. + if (response == null) { + log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de api-get-db2-accounts-client-v2")); + throw new GatewayServiceException("Respuesta invalida del servicio externo de api-get-db2-accounts-client-v2"); + } + + return CustomerAccountMapper.toEntityList(response); + } catch (WebApplicationException ex) { + var errorResponse = ex.getResponse(); + var status = errorResponse.getStatus(); + String errorDetails; + + // Verifica si el Content-Type es HTML + if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) { + errorDetails = "Se recibio una respuesta HTML no esperada del servicio."; + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP %s del servicio de api-get-db2-accounts-client-v2 %s", status, errorDetails))); + } else { + // Si no es HTML, probablemente es un error JSON que sí queremos ver. + var errorBody = errorResponse.readEntity(String.class); + + // Aun así, es buena idea truncarlo por si es muy largo + errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000)); + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP no manejado del servicio de api-get-db2-accounts-client-v2 Status: %s. Body: %s", status, errorDetails))); + } + + throw new GatewayServiceException("Error en el servicio de api-get-db2-accounts-client-v2: " + errorDetails); + } catch (RuntimeException ex) { + // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.). + log.error(LoggerHelper.buildError( + requestId, + String.format("Error de comunicacion con el servicio de api-get-db2-accounts-client-v2: %s", ex.getMessage()) + )); + // Traduce el error técnico a una excepción de la aplicación. + throw new GatewayServiceException("No se pudo comunicar con el servicio externo de api-get-db2-accounts-client-v2"); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java new file mode 100644 index 0000000..4a4b7df --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java @@ -0,0 +1,21 @@ +package infrastructure.client.accounts.api; + +import infrastructure.client.accounts.dto.request.CustomerAccountRequest; +import infrastructure.client.accounts.dto.respone.CustomerAccountResponse; +import infrastructure.interceptor.LoggingFilter; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "customer-accounts") +@RegisterProvider(LoggingFilter.class) +public interface CustomerAccountClient { + @POST + @ClientHeaderParam(name = "appId", value = "${app.appId}") + CustomerAccountResponse readCustomerProducts( + @HeaderParam("requestId") String requestId, + CustomerAccountRequest request + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/request/CustomerAccountRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/request/CustomerAccountRequest.java new file mode 100644 index 0000000..cf4cfac --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/request/CustomerAccountRequest.java @@ -0,0 +1,25 @@ +package infrastructure.client.accounts.dto.request; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class CustomerAccountRequest { + String rif; + String bankCode; + String currencyCode; + String status; + String statusMe; + String productCv; + String product; + String channel; + String service; + String operator; + String phone; + String affiliationStatus; + String counterpartyAccount; + String holder; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/respone/CustomerAccountResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/respone/CustomerAccountResponse.java new file mode 100644 index 0000000..d80951f --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/respone/CustomerAccountResponse.java @@ -0,0 +1,44 @@ +package infrastructure.client.accounts.dto.respone; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; +import java.util.List; + +@Value +@Builder +@RegisterForReflection +public class CustomerAccountResponse { + List data; + int record; + + @Value + @RegisterForReflection + public static class CustomerAccount { + String banco; + String cuenta; + String producto; + String descProd; + String claseCta; + String tipoCta; + String moneda; + BigDecimal saldoDisponible; + String estatus; + String statusMe; + String titular; + BigDecimal maximumPerDay; + BigDecimal maximumPerTransaction; + BigDecimal maximumPerMonth; + BigDecimal maximumMonthlyFee; + BigDecimal minimumPerTransaction; + String operator; + String phone; + String channel; + String service; + String affiliationStatus; + String codeError; + String descError; + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/adapter/EncodeValueAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/adapter/EncodeValueAdapter.java new file mode 100644 index 0000000..f02cdd0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/adapter/EncodeValueAdapter.java @@ -0,0 +1,74 @@ +package infrastructure.client.encodevalue.adapter; + +import application.exception.GatewayServiceException; +import application.exception.ServiceCommunicationException; +import application.port.input.command.EncodeValueCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.repository.IEncodeValuePort; +import infrastructure.client.encodevalue.api.EncodeValueClient; +import infrastructure.mapper.EncodeValueMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.WebApplicationException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Slf4j +@ApplicationScoped +public class EncodeValueAdapter implements IEncodeValuePort { + + private final EncodeValueClient client; + + public EncodeValueAdapter(@RestClient EncodeValueClient client) { + this.client = client; + } + + @Override + public String execute( EncodeValueCommand command, String requestId) { + try { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Encode Value")); + var requestDto = EncodeValueMapper.toRequestDto(command); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de encode value", requestDto)); + var response = client.encodeValue(requestId, requestDto); + + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de encode value", response)); + + // Validación de contrato: asegura que la respuesta no sea nula o inválida. + if (response == null) { + log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de encode value")); + throw new GatewayServiceException("Respuesta invalida del servicio externo de encode value"); + } + + return EncodeValueMapper.toResponseDto(response); + + } catch (WebApplicationException ex) { + var errorResponse = ex.getResponse(); + var status = errorResponse.getStatus(); + String errorDetails; + + // Verifica si el Content-Type es HTML + if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) { + errorDetails = "Se recibio una respuesta HTML no esperada del servicio."; + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP %s del servicio de encode value. %s", status, errorDetails))); + } else { + // Si no es HTML, probablemente es un error JSON que sí queremos ver. + var errorBody = errorResponse.readEntity(String.class); + // Aun así, es buena idea truncarlo por si es muy largo + errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000)); + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP no manejado del servicio de encode value. Status: %s. Body: %s", status, errorDetails))); + } + + throw new GatewayServiceException("Error en el servicio de encode value: " + errorDetails); + } catch (RuntimeException ex) { + // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.). + log.error(LoggerHelper.buildError( + requestId, + String.format("Error de comunicacion con el servicio de encode value: %s", ex) + )); + + throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de encode value", ex); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/api/EncodeValueClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/api/EncodeValueClient.java new file mode 100644 index 0000000..c4c7a22 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/api/EncodeValueClient.java @@ -0,0 +1,21 @@ +package infrastructure.client.encodevalue.api; + +import infrastructure.interceptor.LoggingFilter; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.tempuri.Prueba; +import org.tempuri.PruebaResponse; + +@RegisterRestClient(configKey = "encode-value") +@RegisterProvider(LoggingFilter.class) +public interface EncodeValueClient { + @POST + @ClientHeaderParam(name = "appId", value = "${app.appId}") + PruebaResponse encodeValue( + @HeaderParam("requestId") String requestId, + Prueba request + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/adapter/OtpAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/adapter/OtpAdapter.java new file mode 100644 index 0000000..a37c31b --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/adapter/OtpAdapter.java @@ -0,0 +1,91 @@ +package infrastructure.client.otp.adapter; + +import application.exception.GatewayServiceException; +import application.exception.InvalidTokenException; +import application.port.input.command.ValidateOtpCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.repository.IOtpPort; +import infrastructure.client.otp.api.OtpClient; +import infrastructure.mapper.OtpMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.reactive.RestResponse; + +@Slf4j +@ApplicationScoped +public class OtpAdapter implements IOtpPort { + + private final OtpClient client; + + @Inject + public OtpAdapter(@RestClient OtpClient client) { + this.client = client; + } + + /** + * Implementa la comunicación con la API externa para validar un OTP. + * Se encarga de mapear el DTO, llamar al cliente REST y TRADUCIR errores + * técnicos (de red) o respuestas HTTP inesperadas en excepciones de la aplicación. + * + * @param command El comando de dominio con los datos de validación. + * @param requestId El ID de la solicitud para trazabilidad. + * @return Un objeto {@link RestResponse} que contiene la respuesta HTTP completa de la API. + * @throws GatewayServiceException si ocurre un error de red o la API responde con un error 5xx. + */ + @Override + public RestResponse validateOtp(ValidateOtpCommand command, String requestId) { + try { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para la API privada de validate otp")); + var requestDto = OtpMapper.toRequestDto(command); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de validate otp", requestDto)); + + var response = client.validateOtp( + requestId, + requestDto.getOtpToken(), + requestDto.getAppOrigen(), + requestDto.getLogin(), + requestDto.getIpAddress(), + requestDto.getCanal(), + requestDto.getServicio() + ); + + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de otp", response.getStatus())); + + return response; + } catch(WebApplicationException ex) { + var errorResponse = ex.getResponse(); + var status = errorResponse.getStatus(); + String errorDetails; + + // OTP inválido (respuesta de la privada) + if(status == 403) { + log.error(LoggerHelper.buildError(requestId, String.format("token %s invalido, respuesta HTTP API privada de otp: %s", command.otpToken(), status))); + throw new InvalidTokenException("Token inválido"); + } + + // Verifica si el Content-Type es HTML + if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) { + errorDetails = "Se recibio una respuesta HTML no esperada del servicio."; + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP %s del servicio de otp. %s", status, errorDetails))); + } else { + // Si no es HTML, probablemente es un error JSON que sí queremos ver. + var errorBody = errorResponse.readEntity(String.class); + + // Se trunca por si es el error es muy largo + errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000)); + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP no manejado del servicio de otp. Status: %s. Body: %s", status, errorDetails))); + } + + throw new GatewayServiceException("Error en el servicio de otp: " + errorDetails); + } catch(RuntimeException ex) { + // Error de red (Timeout, no se puede conectar, etc.) + log.error(LoggerHelper.buildError(requestId, String.format("Error de comunicacion con el servicio de otp %s", ex))); + throw new GatewayServiceException("No se pudo comunicar con el servicio externo de otp."); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/api/OtpClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/api/OtpClient.java new file mode 100644 index 0000000..d990de0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/api/OtpClient.java @@ -0,0 +1,28 @@ +package infrastructure.client.otp.api; + +import infrastructure.interceptor.LoggingFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.RestResponse; + +@RegisterRestClient(configKey = "otp") +@RegisterProvider(LoggingFilter.class) +public interface OtpClient { + @GET + @ClientHeaderParam(name = "appId", value = "${app.appId}") + @Path("/{otpToken}/{appOrigen}/{login}/{ipAddress}/{canal}/{servicio}") + RestResponse validateOtp( + @HeaderParam("requestId") String requestId, + @PathParam("otpToken") String otpToken, + @PathParam("appOrigen") String appOrigen, + @PathParam("login") String login, + @PathParam("ipAddress") String ipAddress, + @PathParam("canal") String canal, + @PathParam("servicio") String servicio + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/request/ValidateOtpRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/request/ValidateOtpRequest.java new file mode 100644 index 0000000..3dfb663 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/request/ValidateOtpRequest.java @@ -0,0 +1,19 @@ +package infrastructure.client.otp.dto.request; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class ValidateOtpRequest { +// MsgRqHdr msgRqHdr; + String otpToken; + String appOrigen; + String login; + String ipAddress; + String canal; + String servicio; +// String domain; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/AditionalStatus.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/AditionalStatus.java new file mode 100644 index 0000000..bb96c54 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/AditionalStatus.java @@ -0,0 +1,13 @@ +package infrastructure.client.otp.dto.response; + +import lombok.Value; + +@Value +public class AditionalStatus { + String statusType; + String statusCode; + String statusDesc; + String validationType; + String severity; + String lineNumber; +} diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/Status.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/Status.java new file mode 100644 index 0000000..db7341b --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/Status.java @@ -0,0 +1,22 @@ +package infrastructure.client.otp.dto.response; + +import com.banesco.xmlns.enterpriseobjects.status.AdditionalStatus; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Value; + +import java.math.BigInteger; +import java.util.List; + +@Value +@RegisterForReflection +public class Status { + String statusType; + String statusCode; + String statusDesc; + String applicationName; + BigInteger lineNumber; + List additionalStatus; + String severity; + String statusInd; + String logId; +} diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/ValidateOtpResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/ValidateOtpResponse.java new file mode 100644 index 0000000..51a62e7 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/ValidateOtpResponse.java @@ -0,0 +1,14 @@ +package infrastructure.client.otp.dto.response; + +import com.banesco.xmlns.enterpriseobjects.msgrshdr.MsgRsHdr; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class ValidateOtpResponse { + MsgRsHdr msgRsHdr; + Status status; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/adapter/RegisterSecurityAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/adapter/RegisterSecurityAdapter.java new file mode 100644 index 0000000..3eced53 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/adapter/RegisterSecurityAdapter.java @@ -0,0 +1,89 @@ +package infrastructure.client.registersecurity.adapter; + +import application.port.input.command.RegisterSecurityCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.repository.IRegisterSecurityPort; +import infrastructure.client.registersecurity.api.RegisterSecurityClient; +import infrastructure.mapper.RegisterSecurityMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Slf4j +@ApplicationScoped +public class RegisterSecurityAdapter implements IRegisterSecurityPort { + + private final RegisterSecurityClient client; + private final RegisterSecurityMapper mapper; + + @Inject + public RegisterSecurityAdapter( + @RestClient RegisterSecurityClient client, + RegisterSecurityMapper mapper + ) { + this.client = client; + this.mapper = mapper; + } + + @Override + public void writeTrace(RegisterSecurityCommand command, String requestId) { + try { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para register security")); + var requestDto = mapper.toRequestDto(command); + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de register security (asincrono)", requestDto)); + + client.registerSecurity(requestId, requestDto) + .subscribe().with( + // 1. Callback de ÉXITO (sin cambios) + response -> log.info(LoggerHelper.buildInfoPrivateResponse( + requestId, + String.format("Traza de seguridad registrada. Status: %s", response.getStatus()) + )), + + // 2. Callback de FALLO (con la nueva lógica detallada) + failure -> handleFailure(failure, requestId) + ); + + } catch (Exception e) { + // Este bloque captura errores síncronos (ej. en el mapeo) + log.error(LoggerHelper.buildError( + requestId, + String.format("Error al preparar la llamada para registrar la traza de seguridad: %s", e.getMessage()) + ), e); + } + } + + /** + * Centralizar y detallar el manejo de errores asíncronos. + * + * @param failure La excepción ocurrida. + * @param requestId El ID de la traza. + */ + private void handleFailure(Throwable failure, String requestId) { + if (failure instanceof WebApplicationException ex) { + // Lógica para errores HTTP (4xx, 5xx) - Equivalente al primer catch + var errorResponse = ex.getResponse(); + var status = errorResponse.getStatus(); + String errorDetails; + + if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) { + errorDetails = "Se recibio una respuesta HTML no esperada del servicio."; + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP %s del servicio de register security. %s", status, errorDetails))); + } else { + var errorBody = errorResponse.readEntity(String.class); + errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000)); + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP no manejado del servicio de register security. Status: %s. Body: %s", status, errorDetails))); + } + } else { + // Lógica para otros errores (red, timeout, etc.) - Equivalente al segundo catch + log.error(LoggerHelper.buildError( + requestId, + String.format("Error de comunicacion con el servicio de register security: %s", failure.getMessage()) + ), failure); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/api/RegisterSecurityClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/api/RegisterSecurityClient.java new file mode 100644 index 0000000..5d64c80 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/api/RegisterSecurityClient.java @@ -0,0 +1,20 @@ +package infrastructure.client.registersecurity.api; + +import infrastructure.client.registersecurity.dto.request.RegisterSecurityRequest; +import infrastructure.interceptor.LoggingFilter; +import io.smallrye.mutiny.Uni; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "register-security") +@RegisterProvider(LoggingFilter.class) +public interface RegisterSecurityClient { + @POST + Uni registerSecurity( + @HeaderParam("requestId") String requestId, + RegisterSecurityRequest request + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/dto/request/RegisterSecurityRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/dto/request/RegisterSecurityRequest.java new file mode 100644 index 0000000..d1bad2b --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/dto/request/RegisterSecurityRequest.java @@ -0,0 +1,39 @@ +package infrastructure.client.registersecurity.dto.request; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +import java.util.Date; + +@RegisterForReflection +public record RegisterSecurityRequest ( + String codBan, + String codMon, + String codEve, + String codEve2, + String login, + Date fecHor, + String nacCli, + Integer cedRifCli, + String tipoProductoCli, + String tipoProductoBen, + String productoCli, + String codEmpresa, + String nacBen, + Integer cedBen, + String nombreBen, + String productoBen, + Double monto, + String referencia, + String nroDePago, + String desPago, + String objeto, + Integer tipoRespuesta, + String msgRespuesta, + Integer tiempoRespuesta, + String codFintech, + String cedRifFintech, + String tipoDispositivo, + String desDispositivo, + String ipCli, + String sp +) {} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/adapter/ServiceStatusAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/adapter/ServiceStatusAdapter.java new file mode 100644 index 0000000..2b97fa0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/adapter/ServiceStatusAdapter.java @@ -0,0 +1,86 @@ +package infrastructure.client.servicestatus.adapter; + +import application.exception.GatewayServiceException; +import application.exception.ServiceCommunicationException; +import application.port.input.command.ServiceStatusCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.model.ServiceStatus; +import domain.repository.IStatusPort; +import infrastructure.client.servicestatus.api.ServiceStatusClient; +import infrastructure.mapper.ServiceStatusMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Slf4j +@ApplicationScoped +public class ServiceStatusAdapter implements IStatusPort { + + private final ServiceStatusClient client; + + @Inject + public ServiceStatusAdapter(@RestClient ServiceStatusClient client) { + this.client = client; + } + + /** + * Implementa la comunicación con la API externa para obtener el estado de un servicio. + * + * @param command El comando de dominio con los datos de la consulta. + * @param requestId El ID de la solicitud para trazabilidad. + * @return Un objeto {@link ServiceStatus} con el estado del servicio. + * @throws GatewayServiceException si el servicio externo no es accesible, devuelve un error, + * o la respuesta tiene un formato inesperado. + */ + @Override + public ServiceStatus getServiceStatus(ServiceStatusCommand command, String requestId) { + try { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Service Status")); + var requestDto = ServiceStatusMapper.toRequestDto(command); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de service status", requestDto)); + var response = client.getServiceStatus(requestId, requestDto); + + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de service status", response)); + + // Validación de contrato: asegura que la respuesta no sea nula o inválida. + if (response == null || response.getStatus() == null) { + log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de service status.")); + throw new GatewayServiceException("Respuesta invalida del servicio externo de estado."); + } + + return ServiceStatusMapper.toEntity(response); + + } catch (WebApplicationException ex) { + var errorResponse = ex.getResponse(); + var status = errorResponse.getStatus(); + String errorDetails; + + // Verifica si el Content-Type es HTML + if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) { + errorDetails = "Se recibio una respuesta HTML no esperada del servicio."; + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP %s del servicio de service status. %s", status, errorDetails))); + } else { + // Si no es HTML, probablemente es un error JSON que sí queremos ver. + var errorBody = errorResponse.readEntity(String.class); + // Aun así, es buena idea truncarlo por si es muy largo + errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000)); + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP no manejado del servicio de service status. Status: %s. Body: %s", status, errorDetails))); + } + + throw new GatewayServiceException("Error en el servicio de service status: " + errorDetails); + } catch (RuntimeException ex) { + // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.). + log.error(LoggerHelper.buildError( + requestId, + String.format("Error de comunicacion con el servicio de estado: %s", ex.getMessage()) + )); + + throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de service status", ex); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/api/ServiceStatusClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/api/ServiceStatusClient.java new file mode 100644 index 0000000..d03d52f --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/api/ServiceStatusClient.java @@ -0,0 +1,21 @@ +package infrastructure.client.servicestatus.api; + +import com.banesco.common.infraestructure.web.dto.request.ServiceStatusRequest; +import com.banesco.common.infraestructure.web.dto.response.ServiceStatusResponse; +import infrastructure.interceptor.LoggingFilter; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "get-service-status") +@RegisterProvider(LoggingFilter.class) +public interface ServiceStatusClient { + @POST + @ClientHeaderParam(name = "appId", value = "${app.appId}") + ServiceStatusResponse getServiceStatus( + @HeaderParam("requestId") String requestId, + ServiceStatusRequest request + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/adapter/UserLoginAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/adapter/UserLoginAdapter.java new file mode 100644 index 0000000..11605b5 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/adapter/UserLoginAdapter.java @@ -0,0 +1,75 @@ +package infrastructure.client.userlogin.adapter; + +import application.exception.GatewayServiceException; +import application.exception.ServiceCommunicationException; +import application.port.input.command.UserLoginCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.model.UserLogin; +import domain.repository.IUserLoginPort; +import infrastructure.client.userlogin.api.UserLoginClient; +import infrastructure.mapper.UserLoginMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.WebApplicationException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Slf4j +@ApplicationScoped +public class UserLoginAdapter implements IUserLoginPort { + + private final UserLoginClient client; + private final UserLoginMapper mapper; + + public UserLoginAdapter( + @RestClient UserLoginClient client, + UserLoginMapper mapper + ) { + this.client = client; + this.mapper = mapper; + } + + @Override + public UserLogin execute(UserLoginCommand command, String requestId) { + try { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para la API privada de user login")); + var requestDto = mapper.toRequestDto(command, requestId); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de user login", requestDto)); + var response = client.getUserLogin(requestId, requestDto); + + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de user login", response)); + + if (response == null) { + log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de user login.")); + throw new GatewayServiceException("La respuesta del servicio de user login fue nula."); + } + + return UserLoginMapper.toEntity(response); + } catch (WebApplicationException ex) { + var errorResponse = ex.getResponse(); + var status = errorResponse.getStatus(); + String errorDetails; + + // Verifica si el Content-Type es HTML + if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) { + errorDetails = "Se recibio una respuesta HTML no esperada del servicio."; + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP %s del servicio de user login. %s", status, errorDetails))); + } else { + // Si no es HTML, probablemente es un error JSON que sí queremos ver. + var errorBody = errorResponse.readEntity(String.class); + // Aun así, es buena idea truncarlo por si es muy largo + errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000)); + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP no manejado del servicio de user login. Status: %s. Body: %s", status, errorDetails))); + } + + throw new GatewayServiceException("Error en el servicio de user login: " + errorDetails); + } catch (RuntimeException ex) { + // Error de red (Timeout, no se puede conectar, etc.) + log.error(LoggerHelper.buildError(requestId, String.format("Error de comunicacion con el servicio de user login %s", ex))); + + throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de user login", ex); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/api/UserLoginClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/api/UserLoginClient.java new file mode 100644 index 0000000..600d5c8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/api/UserLoginClient.java @@ -0,0 +1,22 @@ +package infrastructure.client.userlogin.api; + +import com.banesco.common.domain.model.BaseResponse; +import infrastructure.client.userlogin.dto.request.UserLoginRequest; +import infrastructure.client.userlogin.dto.response.UserLoginResponse; +import infrastructure.interceptor.LoggingFilter; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "user-login") +@RegisterProvider(LoggingFilter.class) +public interface UserLoginClient { + @POST + @ClientHeaderParam(name = "appId", value = "${app.appId}") + BaseResponse getUserLogin( + @HeaderParam("requestId") String requestId, + UserLoginRequest userLoginRequest + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/request/UserLoginRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/request/UserLoginRequest.java new file mode 100644 index 0000000..cc63e66 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/request/UserLoginRequest.java @@ -0,0 +1,28 @@ +package infrastructure.client.userlogin.dto.request; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class UserLoginRequest { + DataRequest dataRequest; + + @Value + @Builder + public static class DataRequest { + String requestId; + String sourceChannelCode; + DataUser dataUser; + } + + @Value + @Builder + public static class DataUser { + String customerId; + String customerIdType; + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/response/UserLoginResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/response/UserLoginResponse.java new file mode 100644 index 0000000..ee1ab41 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/response/UserLoginResponse.java @@ -0,0 +1,32 @@ +package infrastructure.client.userlogin.dto.response; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.Value; + +@Data +@NoArgsConstructor +@RegisterForReflection +public class UserLoginResponse { + // --- Campos de la respuesta de DEV --- + String usernameBol; + String usernameBOLE; + + // --- Campos de la respuesta de QA --- + String requestId; + String sourceChannelCode; + DataUser dataUser; + + /** + * DTO anidado para representar el objeto "dataUser" de QA. + */ + @Data + @NoArgsConstructor + @RegisterForReflection + public static class DataUser { + String usernameBol; + String usernameBOLE; + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/adapter/ValidateProfileAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/adapter/ValidateProfileAdapter.java new file mode 100644 index 0000000..72e232e --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/adapter/ValidateProfileAdapter.java @@ -0,0 +1,74 @@ +package infrastructure.client.validateprofile.adapter; + +import application.exception.GatewayServiceException; +import application.port.input.command.ValidateProfileCommand; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.model.ClientProfile; +import domain.repository.IValidateProfilePort; +import infrastructure.client.validateprofile.api.ValidateProfileClient; +import infrastructure.mapper.ValidateProfileMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.WebApplicationException; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Slf4j +@ApplicationScoped +public class ValidateProfileAdapter implements IValidateProfilePort { + + private final ValidateProfileClient client; + + public ValidateProfileAdapter(@RestClient ValidateProfileClient client) { + this.client = client; + } + + @Override + public ClientProfile execute(ValidateProfileCommand command, String requestId) { + try { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Service Status")); + var requestDto = ValidateProfileMapper.toRequestDto(command); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de estado de validate profile", requestDto)); + var response = client.validateProfile(requestId, requestDto); + + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de estado de validate profile", response)); + + // Validación de contrato: asegura que la respuesta no sea nula o inválida. + if (response == null) { + log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de validate profile.")); + throw new GatewayServiceException("Respuesta invalida del servicio externo de validate profile."); + } + + return ValidateProfileMapper.toEntity(response); + + } catch (WebApplicationException ex) { + var errorResponse = ex.getResponse(); + var status = errorResponse.getStatus(); + String errorDetails; + + // Verifica si el Content-Type es HTML + if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) { + errorDetails = "Se recibio una respuesta HTML no esperada del servicio."; + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP %s del servicio validate profile. %s", status, errorDetails))); + } else { + // Si no es HTML, probablemente es un error JSON que sí queremos ver. + var errorBody = errorResponse.readEntity(String.class); + // Aun así, es buena idea truncarlo por si es muy largo + errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000)); + log.error(LoggerHelper.buildError(requestId, + String.format("Error HTTP no manejado del servicio de validate profile. Status: %s. Body: %s", status, errorDetails))); + } + + throw new GatewayServiceException("Error en el servicio de validate profile: " + errorDetails); + } catch (RuntimeException ex) { + // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.). + log.error(LoggerHelper.buildError( + requestId, + String.format("Error de comunicacion con el servicio de validate profile: %s", ex.getMessage()) + )); + // Traduce el error técnico a una excepción de la aplicación. + throw new GatewayServiceException("No se pudo comunicar con el servicio externo de validate profile."); + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/api/ValidateProfileClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/api/ValidateProfileClient.java new file mode 100644 index 0000000..5c00991 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/api/ValidateProfileClient.java @@ -0,0 +1,21 @@ +package infrastructure.client.validateprofile.api; + +import infrastructure.client.validateprofile.dto.request.ValidateProfileRequest; +import infrastructure.client.validateprofile.dto.response.ValidateProfileResponse; +import infrastructure.interceptor.LoggingFilter; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "validate-profile") +@RegisterProvider(LoggingFilter.class) +public interface ValidateProfileClient { + @POST + @ClientHeaderParam(name = "appId", value = "${app.appId}") + ValidateProfileResponse validateProfile( + @HeaderParam("requestId") String requestId, + ValidateProfileRequest validateProfileRequest + ); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/request/ValidateProfileRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/request/ValidateProfileRequest.java new file mode 100644 index 0000000..b730573 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/request/ValidateProfileRequest.java @@ -0,0 +1,14 @@ +package infrastructure.client.validateprofile.dto.request; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class ValidateProfileRequest { + String customerId; + String ipAddress; + String hash; +} diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/response/ValidateProfileResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/response/ValidateProfileResponse.java new file mode 100644 index 0000000..45c91e0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/response/ValidateProfileResponse.java @@ -0,0 +1,30 @@ +package infrastructure.client.validateprofile.dto.response; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class ValidateProfileResponse { + Boolean hasError; + String errorMessage; + String errorCode; + Object data; + + public ValidateProfileResponse() { + this.hasError = false; + this.errorMessage = ""; + this.errorCode = ""; + this.data = null; + } + + public ValidateProfileResponse(Boolean hasError, String errorMessage, String errorCode, Object data) { + this.hasError = hasError; + this.errorMessage = errorMessage; + this.errorCode = errorCode; + this.data = data; + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/AccountAvailabilityConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AccountAvailabilityConfig.java new file mode 100644 index 0000000..fcc58b0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AccountAvailabilityConfig.java @@ -0,0 +1,9 @@ +package infrastructure.config; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "app.account-client.availability.schedule") +public interface AccountAvailabilityConfig { + String begin(); + String finish(); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java new file mode 100644 index 0000000..99eb65a --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java @@ -0,0 +1,88 @@ +package infrastructure.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import domain.model.ErrorCode; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +@Getter +@ApplicationScoped +public class AppConfig { + + @Inject + ObjectMapper objectMapper; + + @ConfigProperty(name = "app.response-json") + private Optional responseJsonString; + + @ConfigProperty(name = "app.print-response", defaultValue = "false") + private Boolean printResponse; + + private List errorCodesList; + private Map errorCodeMap; + + @PostConstruct + public void init() { + log.info("Inicializando configuracion de la aplicacion..."); + + if (responseJsonString.isPresent() && !responseJsonString.get().isBlank()) { + try { + this.errorCodesList = objectMapper.readValue(responseJsonString.get(), new TypeReference>() {}); + this.errorCodeMap = this.errorCodesList.stream() + .collect(Collectors.toMap( + ErrorCode::backendCode, + Function.identity(), + (existing, replacement) -> existing + )); + log.info("Se cargaron {} codigos de error en el mapa.", this.errorCodeMap.size()); + } catch (JsonProcessingException e) { + log.error("Error fatal al parsear la propiedad 'app.response-json'.", e); + this.errorCodesList = Collections.emptyList(); + this.errorCodeMap = Collections.emptyMap(); + } + } else { + log.warn("La propiedad 'app.response-json' no esta definida o esta vacia."); + this.errorCodesList = Collections.emptyList(); + this.errorCodeMap = Collections.emptyMap(); + } + + log.info("--- Validando propiedades de configuracion ---"); + + if (errorCodesList.isEmpty()) { + log.warn("JSON de respuesta no configurado o vacio. El resolvedor de codigos podria no operar correctamente."); + } else { + log.info("Configuracion de 'app.response-json' validada."); + } + + log.info("app.print-response configurado: {}", printResponse); + + log.info("Configuracion completada."); + } + + public List getAppJson() { + return errorCodesList; + } + + public Map getErrorCodeMap() { + return errorCodeMap; + } + + public Boolean getPrintResponse() { + return printResponse; + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/AvailabilityBolConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AvailabilityBolConfig.java new file mode 100644 index 0000000..a0dfd69 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AvailabilityBolConfig.java @@ -0,0 +1,10 @@ +package infrastructure.config; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "app.account-client.availability.schedule.param") +public interface AvailabilityBolConfig { + String codBan(); + String codEve(); + String codServ(); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/DomainConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/DomainConfig.java new file mode 100644 index 0000000..0cbb5ea --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/DomainConfig.java @@ -0,0 +1,8 @@ +package infrastructure.config; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "app.domain.api.public") +public interface DomainConfig { + String payment(); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/OtpConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/OtpConfig.java new file mode 100644 index 0000000..54ad7fc --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/OtpConfig.java @@ -0,0 +1,9 @@ +package infrastructure.config; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "app.otp") +public interface OtpConfig { + String channel(); + String servicio(); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/PostgresRepositoryProducer.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/PostgresRepositoryProducer.java new file mode 100644 index 0000000..8a01fc0 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/PostgresRepositoryProducer.java @@ -0,0 +1,34 @@ +package infrastructure.config; + +import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountConfiguration; +import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountJsonConfig; +import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountPostgresConfig; +import com.banesco.common.infraestructure.repository.PostgresRedisRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.redis.client.RedisClient; +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Produces; + +@ApplicationScoped +@RegisterForReflection(targets = { + CustomerAccountPostgresConfig.class, + CustomerAccountConfiguration.class, + CustomerAccountJsonConfig.class +}) +public class PostgresRepositoryProducer { + + @Inject + RedisClient redisClient; + + @Inject + ObjectMapper objectMapper; + + @Produces + @ApplicationScoped + public PostgresRedisRepository postgresRedisRepository() { + return new PostgresRedisRepository(redisClient, objectMapper); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/RedisRepositoryProducer.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RedisRepositoryProducer.java new file mode 100644 index 0000000..f5a2674 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RedisRepositoryProducer.java @@ -0,0 +1,27 @@ +package infrastructure.config; + +import com.banesco.common.domain.model.redis.UserAccountRedis; +import com.banesco.common.infraestructure.repository.UserAccountRedisRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.redis.client.RedisClient; +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Produces; + +@ApplicationScoped +@RegisterForReflection(targets = {UserAccountRedis.class}) +public class RedisRepositoryProducer { + + @Inject + RedisClient redisClient; + + @Inject + ObjectMapper objectMapper; + + @Produces + @ApplicationScoped + public UserAccountRedisRepository userAccountRedisRepository() { + return new UserAccountRedisRepository(redisClient, objectMapper); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java new file mode 100644 index 0000000..369f104 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java @@ -0,0 +1,11 @@ +package infrastructure.config; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "app.register-security") +public interface RegisterSecurityConfig { + String eventCod(); + String bankCod(); + String curCod(); + String sp(); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/ServiceType.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/ServiceType.java new file mode 100644 index 0000000..a7b676d --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/ServiceType.java @@ -0,0 +1,8 @@ +package infrastructure.config.qualifiers; + + +public enum ServiceType { + BLACKLIST, + COMPANY_DATA, + PAYMENT, +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/SoapService.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/SoapService.java new file mode 100644 index 0000000..6def084 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/SoapService.java @@ -0,0 +1,15 @@ +package infrastructure.config.qualifiers; + +import jakarta.inject.Qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) +public @interface SoapService { + ServiceType value(); +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java b/bus-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java new file mode 100644 index 0000000..530ff19 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java @@ -0,0 +1,12 @@ +package infrastructure.context; + +import jakarta.enterprise.context.RequestScoped; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@RequestScoped +public class TraceIdContext { + private String traceId; +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java new file mode 100644 index 0000000..b67e6b3 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java @@ -0,0 +1,116 @@ +package infrastructure.exception; + +import domain.dto.response.CustomerResponse; +import domain.dto.response.StatusResponse; +import infrastructure.context.TraceIdContext; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.inject.Inject; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.jboss.logging.Logger; + +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Provider +public class ConstraintViolationExceptionHandler implements ExceptionMapper { + + private static final Logger LOG = Logger.getLogger(ConstraintViolationExceptionHandler.class); + private static final String DEFAULT_VALIDATION_ERROR_CODE = "VDE-400"; // Código de fallback + + @Inject + TraceIdContext traceIdContext; + + @Inject + StatusDescriptionResolver statusDescriptionResolver; + + @Override + public Response toResponse(ConstraintViolationException exception) { + var traceId = traceIdContext.getTraceId(); + var violations = exception.getConstraintViolations(); + + if (violations.isEmpty()) { + return buildGenericErrorResponse(traceId, exception); + } + + // Se busca la violación con la mayor prioridad según la lista definida. + var highestPriorityViolation = findHighestPriorityViolation(violations); + + // Extraer el backendCode directamente del mensaje de la anotación. + var backendCode = highestPriorityViolation.getMessage(); + var fieldPath = highestPriorityViolation.getPropertyPath().toString(); + + // Obtenemos el nombre simple del campo para el mensaje. + var jsonProperty = fieldPath.substring(fieldPath.lastIndexOf('.') + 1); + + LOG.warnf("[traceId: %s] Error de validacion en el campo '%s'. Mensaje/Codigo: '%s'", traceId, fieldPath, backendCode); + + // Obtener los detalles del error. + var resolvedCode = statusDescriptionResolver.getCode(backendCode); + + var statusResponse = StatusResponse.builder() + .status("error") + .statusCode(resolvedCode.statusCode()) + .message(resolvedCode.description() + ": " + jsonProperty) + .traceId(traceId) + .build(); + + var response = new CustomerResponse(statusResponse); + + return Response.status(Response.Status.BAD_REQUEST) + .entity(response) + .build(); + } + + private ConstraintViolation findHighestPriorityViolation(Set> violations) { + var violationMap = violations.stream() + .collect(Collectors.toMap( + v -> { + String path = v.getPropertyPath().toString(); + return path.substring(path.lastIndexOf('.') + 1); + }, + v -> v, + // En caso de duplicados, toma el primero + (v1, v2) -> v1 + )); + +// for (var fieldName : FIELD_PRIORITY_ORDER) { +// if (violationMap.containsKey(fieldName)) { +// return violationMap.get(fieldName); +// } +// } + + // Si ninguna violación coincide con la lista de prioridades (fallback), devuelve la primera que encuentre. + return violations.stream().findFirst().get(); + } + + /** + * Construye una respuesta de error genérica en caso de que no se encuentren violaciones específicas. + */ + private Response buildGenericErrorResponse(String traceId, ConstraintViolationException exception) { + var detailsForLog = exception.getConstraintViolations() + .stream() + .map(cv -> cv.getPropertyPath() + ": " + cv.getMessage()) + .collect(Collectors.joining("; ")); + + LOG.warnf("[traceId: %s] Error de validación genérico detectado, %s", traceId, detailsForLog); + + var statusResponse = StatusResponse.builder() + .status("error") + .statusCode("VDE-400") + .message("Error en los datos de entrada. Verifique la información enviada") + .traceId(traceId) + .build(); + + var response = new CustomerResponse(statusResponse); + + return Response.status(Response.Status.BAD_REQUEST) + .entity(response) + .build(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..bee5fca --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java @@ -0,0 +1,83 @@ +package infrastructure.exception; + +import application.exception.GatewayServiceException; +import com.banesco.common.application.exception.BanBackendException; +import domain.dto.response.CustomerResponse; +import domain.model.ErrorCode; +import infrastructure.context.TraceIdContext; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.inject.Inject; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.NotAllowedException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Provider +public class GlobalExceptionHandler implements ExceptionMapper { + + @Inject + TraceIdContext traceIdContext; + + @Inject + StatusDescriptionResolver statusDescriptionResolver; + + @Override + public Response toResponse(Throwable exception) { + final var requestId = traceIdContext.getTraceId(); + ErrorCode resolvedCode; + + log.info("[ERROR: {}] Error de backend no manejado que llego al GlobalExceptionHandler", requestId, exception); + + var exceptionClassName = exception.getClass().getName(); + + if (exception instanceof BanBackendException bbe) { + resolvedCode = statusDescriptionResolver.getCode(bbe.getBackendCode()); + if (resolvedCode == null) { + resolvedCode = statusDescriptionResolver.getCode("500"); + } + } else if (exceptionClassName.equals(GatewayServiceException.class.getName()) || + exceptionClassName.equals(application.exception.ServiceCommunicationException.class.getName())) { + resolvedCode = statusDescriptionResolver.getCode("503-URL"); + + } else if (exception instanceof NotFoundException) { + resolvedCode = statusDescriptionResolver.getCode("ERR-404"); + + } else if (exception instanceof NotAllowedException) { + resolvedCode = statusDescriptionResolver.getCode("405"); + + } else if (isClientError(exception)) { + resolvedCode = statusDescriptionResolver.getCode("ERR-400"); + + } else { + // Default para cualquier otra excepción no controlada + log.error("[ERROR: {}] Error de backend no manejado que llego al GlobalExceptionHandler", requestId, exception); + resolvedCode = statusDescriptionResolver.getCode("500"); + } + + var statusResponse = domain.dto.response.StatusResponse.builder() + .status("error") + .statusCode(resolvedCode.statusCode()) + .message(resolvedCode.description()) + .traceId(requestId) + .build(); + + var response = new CustomerResponse(statusResponse); + + // Es importante devolver el código de estado HTTP correcto. + int httpStatus = Integer.parseInt(resolvedCode.statusCode().split("-")[0]); + + return Response.status(httpStatus) + .entity(response) + .build(); + } + + private boolean isClientError(Throwable throwable) { + return throwable instanceof BadRequestException || + throwable instanceof com.fasterxml.jackson.core.JsonProcessingException || + throwable instanceof NullPointerException; + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java new file mode 100644 index 0000000..d46bdb5 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java @@ -0,0 +1,43 @@ +package infrastructure.filter; + +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import infrastructure.config.AppConfig; +import infrastructure.context.TraceIdContext; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; + +import java.util.stream.Collectors; + + +@Slf4j +@Provider +@Priority(200) +@ApplicationScoped +public class HeadersFilter implements ContainerRequestFilter { + + @Inject + AppConfig appConfig; + + @Inject + TraceIdContext traceIdContext; + + @Override + public void filter(ContainerRequestContext requestContext) { + var requestId = traceIdContext.getTraceId(); + + var multiMappedHeaders = requestContext.getHeaders(); + + var headersMap = multiMappedHeaders.keySet().stream() + .collect(Collectors.toMap( + key -> key, + multiMappedHeaders::getFirst + )); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, LoggerHelper.formatHeaders(headersMap))); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java new file mode 100644 index 0000000..67357b4 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java @@ -0,0 +1,26 @@ +package infrastructure.filter; + +import com.banesco.common.infraestructure.helpers.RequestHelper; +import infrastructure.context.TraceIdContext; +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Provider +@Priority(100) +public class TraceIdFilter implements ContainerRequestFilter { + + @Inject + TraceIdContext traceIdContext; + + @Override + public void filter(ContainerRequestContext requestContext) { + var headerId = requestContext.getHeaderString("requestId"); + var finalTraceId = (headerId != null && !headerId.isBlank()) ? headerId : RequestHelper.randomAlphanumeric(20); + traceIdContext.setTraceId(finalTraceId); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java new file mode 100644 index 0000000..7016427 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java @@ -0,0 +1,36 @@ +package infrastructure.healthcheck; + +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Liveness; + +@Slf4j +@Liveness +@ApplicationScoped +public class ApplicationLivenessCheck implements HealthCheck { + + private final String applicationName; + private final String applicationVersion; + + + public ApplicationLivenessCheck( + @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName, + @ConfigProperty(name = "quarkus.application.version", defaultValue = "unknown") String applicationVersion) { + this.applicationName = applicationName; + this.applicationVersion = applicationVersion; + } + + @Override + public HealthCheckResponse call() { + log.debug("Ejecutando liveness health check: {}", System.currentTimeMillis()); + return HealthCheckResponse.named("API") + .up() + .withData("application", applicationName) + .withData("version", applicationVersion) + .withData("timestamp", System.currentTimeMillis()) + .build(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java~ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java~ new file mode 100644 index 0000000..ae975c8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java~ @@ -0,0 +1,36 @@ +package infrastructure.healthcheck; + +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Liveness; + +@Liveness +@ApplicationScoped +@Slf4j +public class ApplicationLivenessCheck implements HealthCheck { + + private final String applicationName; + private final String applicationVersion; + + + public ApplicationLivenessCheck( + @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName, + @ConfigProperty(name = "quarkus.application.version", defaultValue = "unknown") String applicationVersion) { + this.applicationName = applicationName; + this.applicationVersion = applicationVersion; + } + + @Override + public HealthCheckResponse call() { + log.debug("Ejecutando liveness health check: {}", System.currentTimeMillis()); + return HealthCheckResponse.named("API") + .up() + .withData("application", applicationName) + .withData("version", applicationVersion) + .withData("timestamp", System.currentTimeMillis()) + .build(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java new file mode 100644 index 0000000..2a0636b --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java @@ -0,0 +1,38 @@ +package infrastructure.healthcheck; + +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Startup; + +/** + * Startup Check: Verifica que el contexto de la aplicación (CDI) + * se ha inicializado correctamente y las configuraciones básicas están disponibles. + * Este chequeo le dice a Kubernetes "El arranque interno finalizó", + * permitiendo que los chequeos de Liveness y Readiness tomen el control. + */ +@Slf4j +@Startup +@ApplicationScoped +public class ApplicationStartupCheck implements HealthCheck { + + private final String applicationName; + + public ApplicationStartupCheck( + @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName) { + this.applicationName = applicationName; + } + + @Override + public HealthCheckResponse call() { + log.info("Ejecutando startup health check para {}...", applicationName); + + return HealthCheckResponse.named("Application Startup") + .up() + .withData("application", applicationName) + .withData("status", "Contexto de aplicación (CDI) iniciado.") + .build(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java new file mode 100644 index 0000000..778a2f7 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java @@ -0,0 +1,31 @@ +package infrastructure.interceptor; + +import infrastructure.context.TraceIdContext; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.ext.Provider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +@Provider +public class LoggingFilter implements ClientRequestFilter { + private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class); + + private final TraceIdContext traceIdContext; + + public LoggingFilter(TraceIdContext traceIdContext) { + this.traceIdContext = traceIdContext; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + var requestId = traceIdContext.getTraceId(); + + LOG.info("[{}] Peticion REST a: {} {}", + requestId, + requestContext.getMethod(), + requestContext.getUri()); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/BankingProductMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/BankingProductMapper.java new file mode 100644 index 0000000..bdbfca4 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/BankingProductMapper.java @@ -0,0 +1,28 @@ +package infrastructure.mapper; + +import com.banesco.common.infraestructure.helpers.AccountHelper; +import domain.model.BankingProduct; +import domain.model.CustomerAccount; + +import java.util.List; + +public class BankingProductMapper { + + public static List fromCustomerAccountList(List customerAccount, boolean maskAccount) { + final String USDCurrency = "USD"; + return customerAccount.stream() + .map(data -> BankingProduct.builder() + .productNumber( + maskAccount ? + (USDCurrency.equals(data.getMoneda()) ? + AccountHelper.getTotalMaskedAccountId(data.getCuenta()) + : AccountHelper.getMaskedAccountId(data.getCuenta()) + ) : + data.getCuenta() + ) + .productType(data.getTipoCta()) + .balance(data.getSaldoDisponible()) + .currency(data.getMoneda()) + .build()).toList(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CacheMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CacheMapper.java new file mode 100644 index 0000000..2fbc7c7 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CacheMapper.java @@ -0,0 +1,57 @@ +package infrastructure.mapper; + +import com.banesco.common.domain.model.redis.UserAccountRedis; +import com.banesco.common.infraestructure.helpers.AccountHelper; +import domain.model.CustomerAccount; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Clase de utilidad para mapear (convertir) objetos del dominio + * a objetos específicos para la caché de Redis y viceversa. + */ +public class CacheMapper { + + // Constructor privado para que nadie pueda instanciar esta clase de utilidad. + private CacheMapper() {} + + /** + * Convierte una lista de BankingProduct (modelo de dominio) a una lista + * de UserAccountRedis (modelo de caché). + * + * @param bankingProducts La lista de productos a convertir. + * @param sid El Session ID que se asignará a cada objeto en la caché. + * @param customerId El ID del cliente. + * @return Una lista de UserAccountRedis lista para ser guardada en Redis. + */ + public static List toUserAccountRedisList(List bankingProducts, String sid, String customerId, String otp) { + if (bankingProducts == null || bankingProducts.isEmpty()) { + return Collections.emptyList(); + } + + return bankingProducts.stream() + .map(product -> toUserAccountRedis(product, sid, customerId, otp)) + .collect(Collectors.toList()); + } + + /** + * Lógica de conversión para un único objeto. + */ + private static UserAccountRedis toUserAccountRedis(CustomerAccount product, String sid, String customerId, String otp) { + var redisAccount = new UserAccountRedis(); + redisAccount.setSid(sid); + redisAccount.setCustomerId(customerId); + redisAccount.setClearAccount(product.getCuenta()); + redisAccount.setMaskedAccount(AccountHelper.getMaskedAccountId(product.getCuenta())); + redisAccount.setCurrency(product.getMoneda().trim()); + redisAccount.setAccountType(product.getTipoCta()); + redisAccount.setUsername(customerId); + redisAccount.setOtp(otp); + + return redisAccount; + } + + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CustomerAccountMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CustomerAccountMapper.java new file mode 100644 index 0000000..ed4fe48 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CustomerAccountMapper.java @@ -0,0 +1,63 @@ +package infrastructure.mapper; + +import application.port.input.command.CustomerAccountCommand; +import domain.model.CustomerAccount; +import infrastructure.client.accounts.dto.request.CustomerAccountRequest; +import infrastructure.client.accounts.dto.respone.CustomerAccountResponse; + +import java.util.Collections; +import java.util.List; + +public final class CustomerAccountMapper { + + public static List toEntityList(CustomerAccountResponse response) { + if (response == null || response.getData() == null) { + return Collections.emptyList(); + } + + return response.getData().stream() + .map(data -> CustomerAccount.builder() + .banco(data.getBanco()) + .cuenta(data.getCuenta()) + .producto(data.getProducto()) + .descProd(data.getDescProd()) + .claseCta(data.getClaseCta()) + .tipoCta(data.getTipoCta()) + .moneda(data.getMoneda()) + .saldoDisponible(data.getSaldoDisponible()) + .estatus(data.getEstatus()) + .statusMe(data.getStatusMe()) + .titular(data.getTitular()) + .maximumPerDay(data.getMaximumPerDay()) + .maximumPerTransaction(data.getMaximumPerTransaction()) + .maximumPerMonth(data.getMaximumPerMonth()) + .maximumMonthlyFee(data.getMaximumMonthlyFee()) + .minimumPerTransaction(data.getMinimumPerTransaction()) + .operator(data.getOperator()) + .phone(data.getPhone()) + .channel(data.getChannel()) + .service(data.getService()) + .affiliationStatus(data.getAffiliationStatus()) + .build()).toList(); + } + + public static CustomerAccountRequest toRequestDto(CustomerAccountCommand command) { + return CustomerAccountRequest.builder() + .rif(command.getRif()) + .bankCode(command.getBankCode()) + .currencyCode(command.getCurrencyCode()) + .status(command.getStatus()) + .statusMe(command.getStatusMe()) + .productCv(command.getProductCv()) + .product(command.getProduct()) + .channel(command.getChannel()) + .service(command.getService()) + .operator(command.getOperator()) + .phone(command.getPhone()) + .affiliationStatus(command.getAffiliationStatus()) + .counterpartyAccount(command.getCounterpartyAccount()) + .holder(command.getHolder()) + .build(); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/EncodeValueMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/EncodeValueMapper.java new file mode 100644 index 0000000..6c1d186 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/EncodeValueMapper.java @@ -0,0 +1,18 @@ +package infrastructure.mapper; + +import application.port.input.command.EncodeValueCommand; +import org.tempuri.Prueba; +import org.tempuri.PruebaResponse; + +public class EncodeValueMapper { + public static Prueba toRequestDto(EncodeValueCommand command) { + var prueba = new Prueba(); + prueba.setSApp(command.sApp()); + return prueba; + } + + public static String toResponseDto(PruebaResponse response) { + return response.getPruebaResult(); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/OtpMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/OtpMapper.java new file mode 100644 index 0000000..06b18fd --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/OtpMapper.java @@ -0,0 +1,26 @@ +package infrastructure.mapper; + +import application.port.input.command.ValidateOtpCommand; +import domain.model.Otp; +import infrastructure.client.otp.dto.request.ValidateOtpRequest; +import infrastructure.client.otp.dto.response.ValidateOtpResponse; + +public class OtpMapper { + public static Otp toEntity(ValidateOtpResponse dto) { + return Otp.builder() + .msgRsHdr(dto.getMsgRsHdr()) + .status(dto.getStatus()) + .build(); + } + + public static ValidateOtpRequest toRequestDto(ValidateOtpCommand otp) { + return ValidateOtpRequest.builder() + .servicio(otp.servicio()) + .canal(otp.canal()) + .ipAddress(otp.ipAddress()) + .login(otp.login()) + .appOrigen(otp.appOrigen()) + .otpToken(otp.otpToken()) + .build(); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/PostgresMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/PostgresMapper.java new file mode 100644 index 0000000..6eaa730 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/PostgresMapper.java @@ -0,0 +1,185 @@ +package infrastructure.mapper; + +import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountConfiguration; +import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountJsonConfig; +import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountPostgresConfig; + +import domain.entity.AppConfiguration; +import domain.entity.ConfigItem; +import domain.model.Configuration; + +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Mapeador estático para convertir entidades de la App (JPA) + * a DTOs de "commons" (para Redis). + */ +public class PostgresMapper { + + /** + * Constructor privado para evitar que esta clase de utilidad sea instanciada. + */ + private PostgresMapper() {} + + public static CustomerAccountPostgresConfig mapToCommon(AppConfiguration appEntity) { + if (appEntity == null) { + return null; + } + + var commonDto = new CustomerAccountPostgresConfig(); + + commonDto.setAppId(appEntity.getAppId()); + commonDto.setAppName(appEntity.getAppName()); + commonDto.setApiName(appEntity.getApiName()); + commonDto.setType(appEntity.getType()); + commonDto.setEvent(appEntity.getEvent()); + commonDto.setEncryptionData(appEntity.isEncryptionData()); + + commonDto.setConfiguration(mapConfigurationToCommon(appEntity.getConfiguration())); + + return commonDto; + } + + private static CustomerAccountConfiguration mapConfigurationToCommon(Configuration appConfig) { + if (appConfig == null) { + return null; + } + + var commonConfigDto = new CustomerAccountConfiguration(); + + // Mapea los aliases (son Map, se copian directo) + commonConfigDto.setValidationRules(appConfig.getValidationRules()); + commonConfigDto.setAliases(appConfig.getAliases()); + + // Mapea el mapa de configuración, convirtiendo cada ConfigItem (llama al Nivel 3) + if (appConfig.getConfiguration() != null) { + var commonConfigMap = appConfig.getConfiguration().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> mapConfigItemToCommon(entry.getValue()) // El valor (ConfigItem) se mapea + )); + commonConfigDto.setConfiguration(commonConfigMap); + } + + return commonConfigDto; + } + + /** + * Nivel 3: Mapea un ConfigItem (App) a su DTO CustomerAccountJsonConfig (Commons). + * Como tienen los mismos campos, es una copia directa. + */ + private static CustomerAccountJsonConfig mapConfigItemToCommon(ConfigItem appItem) { + if (appItem == null) { + return null; + } + + var commonItemDto = new CustomerAccountJsonConfig(); + + commonItemDto.setCacheable(appItem.isCacheable()); + commonItemDto.setBankCode(appItem.getBankCode()); + commonItemDto.setCurrencyCode(appItem.getCurrencyCode()); + commonItemDto.setStatus(appItem.getStatus()); + commonItemDto.setStatusMe(appItem.getStatusMe()); + commonItemDto.setProductCv(appItem.getProductCv()); + commonItemDto.setProduct(appItem.getProduct()); + commonItemDto.setChannel(appItem.getChannel()); + commonItemDto.setService(appItem.getService()); + commonItemDto.setOperator(appItem.getOperator()); + commonItemDto.setPhone(appItem.getPhone()); + commonItemDto.setAffiliationStatus(appItem.getAffiliationStatus()); + commonItemDto.setCounterpartyAccount(appItem.getCounterpartyAccount()); + commonItemDto.setHolder(appItem.getHolder()); + commonItemDto.setCodProduct(appItem.getCodProduct()); + commonItemDto.setStatusOperation(appItem.getStatusOperation()); + commonItemDto.setStatusRestriction(appItem.getStatusRestriction()); + commonItemDto.setStatusOperationRestriction(appItem.getStatusOperationRestriction()); + + return commonItemDto; + } + + /** + * Nivel (Inverso): Mapea el DTO de "commons" a la entidad AppConfiguration (App). + * NOTA: ¡Esto requiere que tu entidad AppConfiguration tenga setters! + */ + public static AppConfiguration mapToApp(CustomerAccountPostgresConfig commonDto) { + if (commonDto == null) { + return null; + } + + var appEntity = new AppConfiguration(); + + // Asumiendo que existen setters... (ver nota abajo) + appEntity.setAppId(commonDto.getAppId()); + appEntity.setAppName(commonDto.getAppName()); + appEntity.setApiName(commonDto.getApiName()); + appEntity.setType(commonDto.getType()); + appEntity.setEvent(commonDto.getEvent()); + appEntity.setEncryptionData(commonDto.isEncryptionData()); + + // Mapeo inverso de objetos anidados (Nivel 2) + appEntity.setConfiguration(mapConfigurationToApp(commonDto.getConfiguration())); + + return appEntity; + } + + /** + * Nivel 2 (Inverso): Mapea el DTO de Configuración (Commons) a la clase de Modelo (App). + */ + private static Configuration mapConfigurationToApp(CustomerAccountConfiguration commonConfigDto) { + if (commonConfigDto == null) { + return null; + } + + var appConfig = new Configuration(); + + appConfig.setValidationRules(commonConfigDto.getValidationRules()); + appConfig.setAliases(commonConfigDto.getAliases()); + + // Mapea el mapa de configuración (Nivel 3) + if (commonConfigDto.getConfiguration() != null) { + var appConfigMap = commonConfigDto.getConfiguration().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> mapConfigItemToApp(entry.getValue()) + )); + appConfig.setConfiguration(appConfigMap); + } + + return appConfig; + } + + /** + * Nivel 3 (Inverso): Mapea el DTO de ConfigItem (Commons) a la Entidad (App). + */ + private static ConfigItem mapConfigItemToApp(CustomerAccountJsonConfig commonItemDto) { + if (commonItemDto == null) { + return null; + } + + var appItem = new ConfigItem(); + + // Mapeo 1:1 + appItem.setCacheable(commonItemDto.isCacheable()); + appItem.setBankCode(commonItemDto.getBankCode()); + appItem.setCurrencyCode(commonItemDto.getCurrencyCode()); + appItem.setStatus(commonItemDto.getStatus()); + appItem.setStatusMe(commonItemDto.getStatusMe()); + appItem.setProductCv(commonItemDto.getProductCv()); + appItem.setProduct(commonItemDto.getProduct()); + appItem.setChannel(commonItemDto.getChannel()); + appItem.setService(commonItemDto.getService()); + appItem.setOperator(commonItemDto.getOperator()); + appItem.setPhone(commonItemDto.getPhone()); + appItem.setAffiliationStatus(commonItemDto.getAffiliationStatus()); + appItem.setCounterpartyAccount(commonItemDto.getCounterpartyAccount()); + appItem.setHolder(commonItemDto.getHolder()); + appItem.setCodProduct(commonItemDto.getCodProduct()); + appItem.setStatusOperation(commonItemDto.getStatusOperation()); + appItem.setStatusRestriction(commonItemDto.getStatusRestriction()); + appItem.setStatusOperationRestriction(commonItemDto.getStatusOperationRestriction()); + + return appItem; + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/RegisterSecurityMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/RegisterSecurityMapper.java new file mode 100644 index 0000000..c826212 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/RegisterSecurityMapper.java @@ -0,0 +1,57 @@ +package infrastructure.mapper; + +import application.port.input.command.RegisterSecurityCommand; +import infrastructure.client.registersecurity.dto.request.RegisterSecurityRequest; +import infrastructure.config.RegisterSecurityConfig; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class RegisterSecurityMapper { + + private final RegisterSecurityConfig config; + + @Inject + public RegisterSecurityMapper(RegisterSecurityConfig config) { + this.config = config; + } + + public RegisterSecurityRequest toRequestDto(RegisterSecurityCommand command) { + if (command == null) { + return null; + } + + return new RegisterSecurityRequest( + config.bankCod(), // Dato de configuración + config.curCod(), // Dato de configuración + config.eventCod(), // Dato de configuración + config.eventCod(), // Dato de configuración + command.getLogin(), // Dato del comando + command.getFecHor(), // Dato del comando + command.getNacCli(), // Dato del comando + command.getCedRifCli(), // Dato del comando + command.getTipoProductoCli(), + command.getTipoProductoBen(), + command.getProductoCli(), + command.getCodEmpresa(),// Dato del comando + command.getNacBen(), // Dato del comando + command.getCedBen(), // Dato del comando + command.getNombreBen(), + command.getProductoBen(), // Dato del comando + command.getMonto(), // Dato del comando + command.getReferencia(), // Dato del comando + command.getNroDePago(), // Dato del comando + command.getDesPago(), // Dato del comando + command.getObjeto(), // Dato del comando + command.getTipoRespuesta(), // Dato del comando + command.getMsgRespuesta(), // Dato del comando + command.getTiempoRespuesta(),// Dato del comando + command.getCodFintech(), // Dato del comando + command.getCedRifFintech(), // Dato del comando + command.getTipoDispositivo(),// Dato del comando + command.getDesDispositivo(),// Dato del comando + command.getIpCli(), // Dato del comando + config.sp() // Dato de configuración + ); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ServiceStatusMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ServiceStatusMapper.java new file mode 100644 index 0000000..d490d86 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ServiceStatusMapper.java @@ -0,0 +1,32 @@ +package infrastructure.mapper; + +import application.port.input.command.ServiceStatusCommand; +import com.banesco.common.infraestructure.web.dto.request.ServiceStatusRequest; +import com.banesco.common.infraestructure.web.dto.response.ServiceStatusResponse; +import domain.model.ServiceStatus; + +public class ServiceStatusMapper { + public static ServiceStatus toEntity(ServiceStatusResponse dto) { + return ServiceStatus.builder() + .status(dto.getStatus()) + .build(); + } + + public static ServiceStatusRequest toRequestDto(ServiceStatusCommand command) { + var bankServiceDto = new com.banesco.common.infraestructure.web.dto.request.BankService(); + bankServiceDto.setBankCode(command.bankService().bankCode()); + bankServiceDto.setServiceCode(command.bankService().serviceCode()); + bankServiceDto.setEventCode(command.bankService().eventCode()); + + // Construye y devuelve el DTO principal + var requestDto = new ServiceStatusRequest(); + requestDto.setApplicationId(command.applicationId()); + requestDto.setTransactionId(command.transactionId()); + requestDto.setBankService(bankServiceDto); + return requestDto; + + } + + + +} diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/UserLoginMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/UserLoginMapper.java new file mode 100644 index 0000000..ad1707a --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/UserLoginMapper.java @@ -0,0 +1,74 @@ +package infrastructure.mapper; + +import application.port.input.command.UserLoginCommand; +import com.banesco.common.domain.model.BaseResponse; +import domain.model.UserLogin; +import infrastructure.client.userlogin.dto.request.UserLoginRequest; +import infrastructure.client.userlogin.dto.response.UserLoginResponse; +import infrastructure.config.RegisterSecurityConfig; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +/** + * Clase de utilidad para mapear objetos entre las capas de la aplicación + * para el flujo de validación de listas negras. + */ +@ApplicationScoped +public class UserLoginMapper { + + private final RegisterSecurityConfig config; + + @Inject + public UserLoginMapper(RegisterSecurityConfig config) { + this.config = config; + } + + + /** + * Convierte la respuesta anidada de la API a la entidad de dominio. + * @param dto La respuesta completa de la API, con la estructura BaseResponse. + * @return Un objeto UserLogin del dominio. + */ + public static UserLogin toEntity(BaseResponse dto) { + if (dto == null || dto.getDataResponse() == null) { + return null; + } + + var data = dto.getDataResponse(); + + if (data.getDataUser() != null) { + return UserLogin.builder() + .usernameBol(data.getDataUser().getUsernameBol()) + .usernameBOLE(data.getDataUser().getUsernameBOLE()) + .build(); + } else { + // Es la estructura de DEV + return UserLogin.builder() + .usernameBol(data.getUsernameBol()) + .usernameBOLE(data.getUsernameBOLE()) + .build(); + } + } + + public UserLoginRequest toRequestDto(UserLoginCommand command, String requestId) { + if(command == null) { + return null; + } + + var userDataRequest = UserLoginRequest.DataUser.builder() + .customerId(command.customerId()) + .customerIdType(command.customerIdType()) + .build(); + + var dataRequest = UserLoginRequest.DataRequest.builder() + .requestId(requestId) + .sourceChannelCode(config.eventCod()) + .dataUser(userDataRequest) + .build(); + + return UserLoginRequest.builder() + .dataRequest(dataRequest) + .build(); + + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ValidateProfileMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ValidateProfileMapper.java new file mode 100644 index 0000000..a44c6eb --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ValidateProfileMapper.java @@ -0,0 +1,30 @@ +package infrastructure.mapper; + +import application.port.input.command.ValidateProfileCommand; +import domain.model.ClientProfile; +import infrastructure.client.validateprofile.dto.request.ValidateProfileRequest; +import infrastructure.client.validateprofile.dto.response.ValidateProfileResponse; + +/** + * Clase de utilidad para mapear objetos entre las capas de la aplicación + * para el flujo de validate profile + */ +public class ValidateProfileMapper { + public static ClientProfile toEntity(ValidateProfileResponse dto) { + return ClientProfile.builder() + .errorMessage(dto.getErrorMessage()) + .errorCode(dto.getErrorCode()) + .hasError(dto.getHasError()) + .data(dto.getData()) + .build(); + } + + public static ValidateProfileRequest toRequestDto(ValidateProfileCommand command) { + return ValidateProfileRequest.builder() + .customerId(command.customerId()) + .ipAddress(command.ipAddress()) + .hash(command.hash()) + .build(); + } + +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java b/bus-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java new file mode 100644 index 0000000..15d0606 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java @@ -0,0 +1,45 @@ +package infrastructure.service; + +import domain.model.ErrorCode; +import infrastructure.config.AppConfig; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.util.Optional; + +@ApplicationScoped +public class StatusDescriptionResolver { + + private final AppConfig appConfig; + private final ErrorCode genericError; + + @Inject + public StatusDescriptionResolver(AppConfig appConfig) { + this.appConfig = appConfig; + // Definimos un código de error genérico por si no se encuentra el que buscamos + this.genericError = new ErrorCode("409", "CONFLICT", "409", "CONFLICT", "error"); + } + + /** + * Busca un ErrorCode por su backend_code. + * Si no lo encuentra, devuelve un ErrorCode genérico de "Error Interno". + * + * @param backendCode El código a buscar (ej. "200", "VDE01", "404"). + * @return El ErrorCode encontrado o uno genérico. Nunca es nulo. + */ + public ErrorCode getCode(String backendCode) { + // Usamos Optional.ofNullable para manejar el caso en que el código no exista en el mapa + return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode)) + .orElse(genericError); + } + + /** + * Alternativa manejar el caso "no encontrado" manualmente. + * + * @param backendCode El código a buscar. + * @return Un Optional que estará vacío si no se encuentra el código. + */ + public Optional findCode(String backendCode) { + return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode)); + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png new file mode 100644 index 0000000..9e7463a Binary files /dev/null and b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png differ diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png new file mode 100644 index 0000000..8e1b25d Binary files /dev/null and b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png differ diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/index.html b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000..03837dd --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,28 @@ + + + + + + + API Banesco - Pública Consulta de Producto BIAN + + + + Logo Banesco + + \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json new file mode 100644 index 0000000..71a4f26 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json @@ -0,0 +1,17 @@ +{ + "customerPositionState": { + "customerReference": { + "customerIdType": "J", + "customerId": "000004293" + } + }, + "device": { + "deviceType": "Mobile", + "deviceDescription": "Xiaomi Note 11 PRO", + "deviceIp": "127.0.0.1", + "deviceSessionReference": "prueba" + }, + "tokenAssignmentInstance": { + "tokenIdentificationCode": "" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json new file mode 100644 index 0000000..a90178b --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json @@ -0,0 +1,17 @@ +{ + "customerPositionState": { + "customerReference": { + "customerIdType": "J", + "customerId": "000004293" + } + }, + "device": { + "deviceType": "Mobile", + "deviceDescription": "Xiaomi Note 11 PRO", + "deviceIp": "127.0.0.1", + "deviceSessionReference": "prueba" + }, + "tokenAssignmentInstance": { + "tokenIdentificationCode": "12345678" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json new file mode 100644 index 0000000..7f92dbf --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json @@ -0,0 +1,85 @@ +{ + "appName": "BANCA_MOVIL", + "apiName": "bus-evaluate-customer-product", + "type": "external", + "event": "CPROD", + "encryptionData": true, + "configuration": { + "validationRules": { + "serviceStatus": false, + "getUserLogin": false, + "encodeValue": false, + "validateOtp": false, + "validateProfile": false + }, + "aliases": { + "V": "V", + "E": "V", + "J": "J", + "G": "J", + "C": "J" + }, + "configuration": { + "all": { + "cacheable": true, + "bankCode": "01", + "currencyCode": "BS", + "status": "A", + "statusMe": "", + "productCv": "", + "product": "", + "channel": "", + "service": "", + "operator": "", + "phone": "", + "affiliationStatus": "", + "counterpartyAccount": "", + "holder": "", + "codProduct": "", + "statusOperation": "", + "statusRestriction": "", + "statusOperationRestriction": "" + }, + "BANCA_MOVIL_V": { + "cacheable": true, + "bankCode": "", + "currencyCode": "", + "status": "ACTBSUSD", + "statusMe": "", + "productCv": "CVFL", + "product": "", + "channel": "", + "service": "", + "operator": "", + "phone": "", + "affiliationStatus": "", + "counterpartyAccount": "", + "holder": "TIT", + "codProduct": "", + "statusOperation": "", + "statusRestriction": "", + "statusOperationRestriction": "" + }, + "BANCA_MOVIL_J": { + "cacheable": true, + "bankCode": "", + "currencyCode": "", + "status": "ACTBSUSD", + "statusMe": "", + "productCv": "CVFL", + "product": "", + "channel": "APP", + "service": "P2P", + "operator": "", + "phone": "", + "affiliationStatus": "A", + "counterpartyAccount": "PAG", + "holder": "TIT", + "codProduct": "", + "statusOperation": "", + "statusRestriction": "", + "statusOperationRestriction": "" + } + } + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json new file mode 100644 index 0000000..1b26bf5 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "400", + "message": "Configuracion no encontrada", + "traceId": "YxwjZEbfHuYc3wG4jDdN" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json new file mode 100644 index 0000000..d42706e --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "409", + "message": "CONFLICT", + "traceId": "b81LdZyGXYCCcLvyaQdz" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json new file mode 100644 index 0000000..5edef7d --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json @@ -0,0 +1,24 @@ +{ + "statusResponse": { + "status": "ok", + "statusCode": "200", + "message": "Operacion Exitosa", + "traceId": "U2VzaLNEEPeX9ODZiRnC" + }, + "customerPositionState": { + "bankingProducts": [ + { + "productNumber": "01340552220001000249", + "productType": "DDA", + "balance": 0.84, + "currency": "BS " + }, + { + "productNumber": "01341740730001281663", + "productType": "DDA", + "balance": 0.00, + "currency": "USD" + } + ] + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json new file mode 100644 index 0000000..f442cbf --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json @@ -0,0 +1,24 @@ +{ + "statusResponse": { + "status": "ok", + "statusCode": "200", + "message": "Operacion Exitosa", + "traceId": "SKeIGjD1lWmJD992LfKZ" + }, + "customerPositionState": { + "bankingProducts": [ + { + "productNumber": "0134************0249", + "productType": "DDA", + "balance": 0.84, + "currency": "BS " + }, + { + "productNumber": "********************", + "productType": "DDA", + "balance": 0.00, + "currency": "USD" + } + ] + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json new file mode 100644 index 0000000..881be26 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "503", + "message": "Incidencia con la resolución de CI", + "traceId": "qV3cYzb5SNIGKbozmF6T" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json new file mode 100644 index 0000000..342d52e --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json @@ -0,0 +1,11 @@ +{ + "statusResponse": { + "status": "ok", + "statusCode": "200", + "message": "Operacion Exitosa", + "traceId": "5YJYkSZdRA0El6QaBjr4" + }, + "customerPositionState": { + "bankingProducts": [] + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json new file mode 100644 index 0000000..70149a3 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "500", + "message": "Uso interno", + "traceId": "oHsLZM4xSVVC8ZoU6yDF" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json new file mode 100644 index 0000000..cda132e --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "401", + "message": "VRN01 - Fallo en la validación del perfil del cliente", + "traceId": "gLoRYYHkZONFOcntMvfn" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json new file mode 100644 index 0000000..cf5098d --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "VRN07", + "message": "VRN07 – Codigo de OTP Invalido", + "traceId": "xJPt6yoN6LA8QJxJpGC6" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json new file mode 100644 index 0000000..af8ed48 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "VRN04", + "message": "Servicio en horario de mantenimiento", + "traceId": "xif7wH0xUbIB3e6cHcr3" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json new file mode 100644 index 0000000..331e8b7 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "AUTH02", + "message": "Usuario no existe", + "traceId": "uAWXR6yNPa6IlQ51FOAU" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json new file mode 100644 index 0000000..ecb3a78 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "AUTH01", + "message": "Usuario no existe", + "traceId": "jptc0JT9mh8ioDhZeZfa" + } +} \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/application.yml b/bus-evaluate-customer-product/src/main/resources/application.yml new file mode 100644 index 0000000..49635e8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/application.yml @@ -0,0 +1,198 @@ +quarkus: + log: + level: INFO + http: + port: 8082 + idle-timeout: 30s + non-application-root-path: /actuator + application: + name: bus-evaluate-customer-product + version: 1.0.0 + smallrye-openapi: + path: /openapi + enable: true + info-title: bus-evaluate-customer-product + info-version: 1.0.0 + info-description: 'API Bus evaluate customer accounts' + swagger-ui: + path: /swagger-ui + always-include: true + redis: + hosts: "redis://localhost:6379" + password: redis + timeout: 3S + max-pool-size: 32 + max-pool-waiting: 100 + datasource: + db-kind: postgresql + jdbc: + url: jdbc:postgresql://localhost:5433/apiban001 + # Tamaño del Pool + min-size: 4 # Conexiones mínimas que el pool mantendrá abiertas. + max-size: 10 # Máximo de conexiones permitidas. ¡Ajusta esto según tu carga! + initial-size: 4 # Conexiones que se crearán al iniciar la aplicación. + # Tiempos de Espera (Timeouts) + acquisition-timeout: 8s # Tiempo máximo (8 seg) que un hilo esperará por una conexión si el pool está lleno. + idle-removal-interval: 30s # Cierra conexiones inactivas (por encima de min-size) cada 30 segundos. + max-lifetime: 30m # Tiempo máximo de vida de una conexión (10 min) para forzar su renovación. + background-validation-interval: 30s # Valida conexiones inactivas en segundo plano cada 30 segundos. + validation-query: "SELECT 1" + username: us3r001 + password: 8gbNQ.7XZK5c + rest-client: + get-service-status: + url: http://api-get-service-status-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/service/status + scope: jakarta.inject.Singleton + user-login: + #(quarkus) +# url: http://api-get-user-login-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/user/login + # (spring) + url: http://api-get-user-login-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/user/login/ + scope: jakarta.inject.Singleton + validate-profile: + url: http://api-validate-profile-client-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/security/profile/validate + scope: jakarta.inject.Singleton + encode-value: + url: http://api-encode-value-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/security/encode + scope: jakarta.inject.Singleton + otp: + url: http://api-validate-otp-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/otp/validate + scope: jakarta.inject.Singleton + customer-accounts: + url: http://api-get-db2-accounts-client-v2-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/accounts + scope: jakarta.inject.Singleton + register-security: + url: http://api-register-security-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/register-security/save + scope: jakarta.inject.Singleton +app: + appId: BUS_EVALUATE_CUSTOMER_PRODUCT + redis: + expiration: 300 + domain: + api: + public: + payment: osbp2p + otp: + channel: 2 + servicio: APIFICACION + account-client: + availability: + schedule: + param: + cod-ban: 01 + cod-eve: P2PVUEL + cod-serv: APIFI + begin: 10:00:00 + finish: 23:00:00 + register-security: + event-cod: CPROD + bank-cod: 01 + cur-cod: BS + sp: spAPI_ConsultaProducto + response-json: | + [ + { + "backend_code": "default", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN_CONFIG_NOT_FOUND", + "http_code": "400", + "status_code": "400", + "description": "Configuracion no encontrada", + "status": "error" + }, + { + "backend_code": "405", + "http_code": "405", + "status_code": "405", + "description": "Metodo no permitido", + "status": "error" + }, + { + "backend_code": "200", + "http_code": "200", + "status_code": "200", + "description": "Operacion exitosa", + "status": "ok" + }, + { + "backend_code": "204", + "http_code": "200", + "status_code": "200", + "description": "Cliente sin productos", + "status": "ok" + }, + { + "backend_code": "503-URL", + "http_code": "503", + "status_code": "503", + "description": "Incidencia con la resolución de CI", + "status": "error" + }, + { + "backend_code": "500", + "http_code": "503", + "status_code": "500", + "description": "Uso interno", + "status": "error" + }, + { + "backend_code": "406", + "http_code": "406", + "status_code": "406", + "description": "Respuesta no aceptable", + "status": "error" + }, + { + "backend_code": "VDE01", + "status_code": "VDE01", + "http_code": "BAD_REQUEST", + "description": "Error en dato de entrada obligatorio" + }, + { + "backend_code": "VRN01", + "http_code": "401", + "status_code": "401", + "description": "VRN01 - Fallo en la validación del perfil del cliente", + "status": "error" + }, + { + "backend_code": "VRN04", + "http_code": "503", + "status_code": "VRN04", + "description": "Servicio en horario de mantenimiento", + "status": "error" + }, + { + "backend_code": "VRN07", + "http_code": "401", + "status_code": "VRN07", + "description": "VRN07 – Codigo de OTP Invalido", + "status": "error" + }, + { + "backend_code": "409", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN19", + "http_code": "401", + "status_code": "AUTH01", + "description": "Usuario no existe", + "status": "error" + }, + { + "backend_code": "VRN20", + "http_code": "401", + "status_code": "AUTH02", + "description": "Usuario no existe", + "status": "error" + } + ] \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/configmap.yaml b/bus-evaluate-customer-product/src/main/resources/configmap.yaml new file mode 100644 index 0000000..b20457f --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/configmap.yaml @@ -0,0 +1,246 @@ +# =================================================================== +# CONFIGURACIÓN GENERAL DE QUARKUS +# =================================================================== + +# Nombre de la aplicación. +quarkus.application.name: bus-evaluate-customer-product +# Versión de la aplicación. +quarkus.application.version: 1.0.0 +# Puerto HTTP en el que se ejecutará la aplicación. +quarkus.http.port: 8080 +# Tiempo máximo de inactividad antes de que el servidor cierre una conexión HTTP. +quarkus.http.idle-timeout: 30s +# Ruta raíz para los endpoints que no son de la aplicación (ej. /health, /metrics). +quarkus.http.non-application-root-path: /actuator + +# =================================================================== +# DOCUMENTACIÓN DE API (OPENAPI & SWAGGER UI) +# =================================================================== + +# Habilita la generación de la especificación OpenAPI. +quarkus.smallrye-openapi.enable: true +# Ruta donde se expondrá la especificación OpenAPI (formato JSON/YAML). +quarkus.smallrye-openapi.path: /openapi +# Título que se mostrará en la documentación de la API. +quarkus.smallrye-openapi.info-title: bus-evaluate-customer-product +# Versión que se mostrará en la documentación de la API. +quarkus.smallrye-openapi.info-version: 1.0.0 +# Descripción general de la API para la documentación. +quarkus.smallrye-openapi.info-description: API Bus evaluate customer accounts + +# Habilita la interfaz de usuario de Swagger. +quarkus.swagger-ui.always-include: true +# Ruta donde estará disponible la interfaz de Swagger UI. +quarkus.swagger-ui.path: /swagger-ui + +# =================================================================== +# CONFIGURACIÓN DE REDIS +# =================================================================== + +# URL de conexión al servidor de Redis. +quarkus.redis.hosts: redis://localhost:6379 +# Contraseña para la autenticación en Redis (si es necesaria). +quarkus.redis.password: redis +# Tiempo máximo de espera para las operaciones de Redis. +quarkus.redis.timeout: 3S +# Tamaño máximo del pool de conexiones hacia Redis. +quarkus.redis.max-pool-size: 32 +# Tiempo máximo de espera para obtener una conexión del pool. +quarkus.redis.max-pool-waiting: 100 + +# =================================================================== +# CONFIGURACIÓN DE LA BASE DE DATOS (POSTGRESQL) +# =================================================================== + +# Tipo de base de datos a utilizar. +quarkus.datasource.db-kind: postgresql +# URL de conexión JDBC a la base de datos. +quarkus.datasource.jdbc.url: jdbc:postgresql://localhost:5433/apiban001 +# Tamaño del Pool +# Conexiones mínimas que el pool mantendrá abiertas. +quarkus.datasource.jdbc.min-size: 4 +# Máximo de conexiones permitidas. ¡Ajusta esto según tu carga! +quarkus.datasource.jdbc.max-size: 10 +# Conexiones que se crearán al iniciar la aplicación. +quarkus.datasource.jdbc.initial-size: 4 +# Tiempos de Espera (Timeouts) +# Tiempo máximo (8 seg) que un hilo esperará por una conexión si el pool está lleno. +quarkus.datasource.jdbc.acquisition-timeout: 8s +# Cierra conexiones inactivas (por encima de min-size) cada 30 segundos. +quarkus.datasource.jdbc.idle-removal-interval: 30s +# Tiempo máximo de vida de una conexión (30 min) para forzar su renovación. +quarkus.datasource.jdbc.max-lifetime: 30m +# Valida conexiones inactivas en segundo plano cada 30 segundos. +quarkus.datasource.jdbc.background-validation-interval: 30s +quarkus.datasource.jdbc.validation-query: SELECT 1 + + + +# Usuario para la conexión a la base de datos. +quarkus.datasource.username: ------- +# Contraseña para la conexión a la base de datos. +quarkus.datasource.password: -------- + +# =================================================================== +# CONFIGURACIÓN DE CLIENTES REST +# =================================================================== + +# --- Cliente para verificar el estado de disponibilidad de un servicio. +quarkus.rest-client.get-service-status.url: http://api-get-service-status-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/service/status +quarkus.rest-client.get-service-status.scope: jakarta.inject.Singleton + +# --- Cliente para el inicio de sesión de usuario. +quarkus.rest-client.user-login.url: http://api-get-user-login-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/user/login +quarkus.rest-client.user-login.scope: jakarta.inject.Singleton + +# --- Cliente para validar el perfil de un cliente. +quarkus.rest-client.validate-profile.url: http://api-validate-profile-client-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/profile/validate +quarkus.rest-client.validate-profile.scope: jakarta.inject.Singleton + +# --- Cliente para codificar valores. +quarkus.rest-client.encode-value.url: http://api-encode-value-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/encode +quarkus.rest-client.encode-value.scope: jakarta.inject.Singleton + +# --- Cliente para validar el token OTP. +quarkus.rest-client.otp.url: http://api-validate-otp-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/otp/validate +quarkus.rest-client.otp.scope: jakarta.inject.Singleton + +# --- Cliente para obtener las cuentas de un cliente desde DB2. +quarkus.rest-client.customer-accounts.url: http://api-get-db2-accounts-client-v2-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/accounts +quarkus.rest-client.customer-accounts.scope: jakarta.inject.Singleton + +# --- Cliente para registrar la traza de seguridad. +quarkus.rest-client.register-security.url: http://api-register-security-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/register-security/save +quarkus.rest-client.register-security.scope: jakarta.inject.Singleton + +# =================================================================== +# PROPIEDADES ESPECÍFICAS DE LA APLICACIÓN +# =================================================================== + +# Identificador único de la aplicación. +app.appId: BUS_EVALUATE_CUSTOMER_PRODUCTS +# (Propiedad personalizada) Tiempo de expiración por defecto para las claves en caché. +app.redis.expiration: 300 +# Dominio específico de la aplicación. +app.domain.api.public.payment: osbp2p + +# --- Parámetros para el servicio de OTP --- +# Canal utilizado para la validación OTP. +app.otp.channel: 2 +# Nombre del servicio que solicita la validación OTP. +app.otp.servicio: APIFICACION + +# --- Parámetros para la ventana de disponibilidad del servicio --- +app.account-client.availability.schedule.param.cod-ban: 1 +app.account-client.availability.schedule.param.cod-eve: P2PVUEL +app.account-client.availability.schedule.param.cod-serv: APIFI +# Hora de inicio de la ventana de disponibilidad +app.account-client.availability.schedule.begin: 10:00:00 +# Hora de fin de la ventana de disponibilidad +app.account-client.availability.schedule.finish: 23:00:00 + +# --- Parámetros para la Traza de Seguridad --- +# Código de evento para la traza. +app.register-security.event-cod: CPROD +# Código de banco para la traza. +app.register-security.bank-cod: 01 +# Código de moneda para la traza. +app.register-security.cur-cod: BS +# Nombre del Stored Procedure para la traza. +app.register-security.sp: spAPI_ConsultaProducto + +# --- Mapeo centralizado de códigos de respuesta de la aplicación --- +app.response-json: | + [ + { + "backend_code": "default", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN_CONFIG_NOT_FOUND", + "http_code": "400", + "status_code": "400", + "description": "Configuracion no encontrada", + "status": "error" + }, + { + "backend_code": "200", + "http_code": "200", + "status_code": "200", + "description": "Operacion exitosa", + "status": "ok" + }, + { + "backend_code": "204", + "http_code": "200", + "status_code": "204", + "description": "Cliente sin productos", + "status": "ok" + }, + { + "backend_code": "503-URL", + "http_code": "503", + "status_code": "503", + "description": "Incidencia con la resolución de CI", + "status": "error" + }, + { + "backend_code": "500", + "http_code": "503", + "status_code": "500", + "description": "Uso interno", + "status": "error" + }, + { + "backend_code": "406", + "http_code": "406", + "status_code": "406", + "description": "Respuesta no aceptable", + "status": "error" + }, + { + "backend_code": "VRN01", + "http_code": "401", + "status_code": "401", + "description": "VRN01 - Fallo en la validación del perfil del cliente", + "status": "error" + }, + { + "backend_code": "VRN04", + "http_code": "503", + "status_code": "VRN04", + "description": "Servicio en horario de mantenimiento", + "status": "error" + }, + { + "backend_code": "VRN07", + "http_code": "401", + "status_code": "VRN07", + "description": "VRN07 – Codigo de OTP Invalido", + "status": "error" + }, + { + "backend_code": "409", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN19", + "http_code": "401", + "status_code": "AUTH01", + "description": "Usuario no existe", + "status": "error" + }, + { + "backend_code": "VRN20", + "http_code": "401", + "status_code": "AUTH02", + "description": "Usuario no existe", + "status": "error" + } + ] \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.old b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.old new file mode 100644 index 0000000..349a8fc --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.old @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OSB Service + + + + + \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.wsdl b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.wsdl new file mode 100644 index 0000000..9bdf0b8 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.wsdl @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OSB Service + + + + + \ No newline at end of file diff --git a/bus-evaluate-customer-product/src/main/resources/wsdl/PruebaService.wsdl b/bus-evaluate-customer-product/src/main/resources/wsdl/PruebaService.wsdl new file mode 100644 index 0000000..62066f2 --- /dev/null +++ b/bus-evaluate-customer-product/src/main/resources/wsdl/PruebaService.wsdl @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rec-evaluate-customer-product/.dockerignore b/rec-evaluate-customer-product/.dockerignore new file mode 100644 index 0000000..94810d0 --- /dev/null +++ b/rec-evaluate-customer-product/.dockerignore @@ -0,0 +1,5 @@ +* +!target/*-runner +!target/*-runner.jar +!target/lib/* +!target/quarkus-app/* \ No newline at end of file diff --git a/rec-evaluate-customer-product/.gitignore b/rec-evaluate-customer-product/.gitignore new file mode 100644 index 0000000..91a800a --- /dev/null +++ b/rec-evaluate-customer-product/.gitignore @@ -0,0 +1,45 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ +# TLS Certificates +.certs/ diff --git a/rec-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties b/rec-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/rec-evaluate-customer-product/.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.11/apache-maven-3.9.11-bin.zip diff --git a/rec-evaluate-customer-product/CHANGELOG.md b/rec-evaluate-customer-product/CHANGELOG.md new file mode 100644 index 0000000..dcb4852 --- /dev/null +++ b/rec-evaluate-customer-product/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +Todos los cambios notables en este proyecto se documentarán en este archivo. + +El formato se basa en [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +y este proyecto se adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v1.0.0 - UNRELEASED +### Updated +- Migración completada a Quarkus 3.25.3 \ No newline at end of file diff --git a/rec-evaluate-customer-product/README.md b/rec-evaluate-customer-product/README.md new file mode 100644 index 0000000..319f10a --- /dev/null +++ b/rec-evaluate-customer-product/README.md @@ -0,0 +1,68 @@ +# rec-evaluate-customer-product + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: . + +## Running the application in dev mode + +You can run your application in dev mode that enables live coding using: + +```shell script +./mvnw quarkus:dev +``` + +> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at . + +## Packaging and running the application + +The application can be packaged using: + +```shell script +./mvnw package +``` + +It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory. + +The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`. + +If you want to build an _über-jar_, execute the following command: + +```shell script +./mvnw package -Dquarkus.package.jar.type=uber-jar +``` + +The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`. + +## Creating a native executable + +You can create a native executable using: + +```shell script +./mvnw package -Dnative +``` + +Or, if you don't have GraalVM installed, you can run the native executable build in a container using: + +```shell script +./mvnw package -Dnative -Dquarkus.native.container-build=true +``` + +<<<<<<< HEAD +You can then execute your native executable with: `./target/rec-customer-accounts-1.0.0-SNAPSHOT-runner` +======= +You can then execute your native executable with: `./target/rec-customer-accounts-1.0.0-runner` +>>>>>>> testing + +If you want to learn more about building native executables, please consult . + +## Related Guides + +- Hibernate Validator ([guide](https://quarkus.io/guides/validation)): Validate object properties (field, getter) and method parameters for your beans (REST, CDI, Jakarta Persistence) +- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it +- YAML Configuration ([guide](https://quarkus.io/guides/config-yaml)): Use YAML to configure your Quarkus application +<<<<<<< HEAD +======= +- SmallRye Health ([guide](https://quarkus.io/guides/smallrye-health)): Monitor service health +>>>>>>> testing diff --git a/rec-evaluate-customer-product/TASKS.md b/rec-evaluate-customer-product/TASKS.md new file mode 100644 index 0000000..e69de29 diff --git a/rec-evaluate-customer-product/mvnw b/rec-evaluate-customer-product/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/rec-evaluate-customer-product/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/rec-evaluate-customer-product/mvnw.cmd b/rec-evaluate-customer-product/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/rec-evaluate-customer-product/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/rec-evaluate-customer-product/pom.xml b/rec-evaluate-customer-product/pom.xml new file mode 100644 index 0000000..e393a13 --- /dev/null +++ b/rec-evaluate-customer-product/pom.xml @@ -0,0 +1,154 @@ + + + 4.0.0 + com.banesco + rec-evaluate-customer-product + rec-evaluate-customer-product + API REST Customer Product (consulta de cuentas) REC + 1.0.0 + + 3.14.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.25.3 + true + 3.5.3 + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + com.banesco + commons + 1.0 + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-swagger-ui + + + io.quarkus + quarkus-rest-client-jackson + + + io.quarkus + quarkus-config-yaml + + + io.quarkus + quarkus-smallrye-health + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-junit5 + test + + + org.projectlombok + lombok + 1.18.38 + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + true + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + native + + + + false + false + true + + + + diff --git a/rec-evaluate-customer-product/scripts/native/Dockerfile b/rec-evaluate-customer-product/scripts/native/Dockerfile new file mode 100644 index 0000000..d81a3ad --- /dev/null +++ b/rec-evaluate-customer-product/scripts/native/Dockerfile @@ -0,0 +1,12 @@ +FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 +RUN mkdir -p /work +ENV TZ="America/Caracas" +ENV LANGUAGE='en_US:en' +VOLUME /tmp +COPY /file/*-runner /work/recCustomerAccounts +RUN chmod -R 775 /work +RUN ls -ltra /work/ +EXPOSE 8080 +WORKDIR /work/ + +ENTRYPOINT ["./recCustomerAccounts", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/rec-evaluate-customer-product/scripts/native/Dockerfile~ b/rec-evaluate-customer-product/scripts/native/Dockerfile~ new file mode 100644 index 0000000..6c0f979 --- /dev/null +++ b/rec-evaluate-customer-product/scripts/native/Dockerfile~ @@ -0,0 +1,12 @@ +FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 +RUN mkdir -p /work +ENV TZ="America/Caracas" +ENV LANGUAGE='en_US:en' +VOLUME /tmp +COPY /file/*-runner /work/busCustomerAccounts +RUN chmod -R 775 /work +RUN ls -ltra /work/ +EXPOSE 8080 +WORKDIR /work/ + +ENTRYPOINT ["./busCustomerAccounts", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/rec-evaluate-customer-product/scripts/native/file/rec-evaluate-customer-product-1.0.0-runner b/rec-evaluate-customer-product/scripts/native/file/rec-evaluate-customer-product-1.0.0-runner new file mode 100644 index 0000000..d968959 Binary files /dev/null and b/rec-evaluate-customer-product/scripts/native/file/rec-evaluate-customer-product-1.0.0-runner differ diff --git a/rec-evaluate-customer-product/src/main/Main.java b/rec-evaluate-customer-product/src/main/Main.java new file mode 100644 index 0000000..dc7558e --- /dev/null +++ b/rec-evaluate-customer-product/src/main/Main.java @@ -0,0 +1,14 @@ +package main; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.annotations.QuarkusMain; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@QuarkusMain +public class Main { + public static void main(String[] args) { + log.info("Running rec-evaluate-customer-product..."); + Quarkus.run(args); + } +} diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.jvm b/rec-evaluate-customer-product/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..dff602a --- /dev/null +++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.jvm @@ -0,0 +1,98 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/rec-customer-accounts-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override +# the default JVM options, use `JAVA_OPTS_APPEND` to append options +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi9/openjdk-17:1.23 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar b/rec-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar new file mode 100644 index 0000000..d9ad43f --- /dev/null +++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar @@ -0,0 +1,94 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package -Dquarkus.package.jar.type=legacy-jar +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/rec-customer-accounts-legacy-jar . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-legacy-jar +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-legacy-jar +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override +# the default JVM options, use `JAVA_OPTS_APPEND` to append options +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi9/openjdk-17:1.23 + +ENV LANGUAGE='en_US:en' + + +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/quarkus-run.jar + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.native b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..1b065f2 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native @@ -0,0 +1,29 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./mvnw package -Dnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/rec-customer-accounts . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts +# +# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.6` base image is based on UBI 9. +# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`. +### +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.6 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root --chmod=0755 target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.native-micro b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native-micro new file mode 100644 index 0000000..1e72495 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native-micro @@ -0,0 +1,32 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# It uses a micro base image, tuned for Quarkus native executables. +# It reduces the size of the resulting container image. +# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. +# +# Before building the container image run: +# +# ./mvnw package -Dnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/rec-customer-accounts . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts +# +# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9. +# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`. +### +FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root --chmod=0755 target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java b/rec-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java new file mode 100644 index 0000000..61dd76a --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java @@ -0,0 +1,12 @@ +package application.exception; + +public class GatewayServiceException extends RuntimeException { + public GatewayServiceException(String message) { + super(message); + } + + public GatewayServiceException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java new file mode 100644 index 0000000..0ccf895 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java @@ -0,0 +1,7 @@ +package application.exception; + +public class InvalidTokenException extends RuntimeException { + public InvalidTokenException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java~ b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java~ new file mode 100644 index 0000000..4c9c2c9 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java~ @@ -0,0 +1,7 @@ +package application.exception; + +public class InvalidTokenException extends RuntimeException { + public InvalidTokenException(String message) { + super(message); + } +} diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java b/rec-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java new file mode 100644 index 0000000..1b55999 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java @@ -0,0 +1,7 @@ +package application.exception; + +public class ServiceCommunicationException extends GatewayServiceException { + public ServiceCommunicationException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java b/rec-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java new file mode 100644 index 0000000..2f4831d --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java @@ -0,0 +1,25 @@ +package application.helper; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@Slf4j +public class CustomerHelper { + /** + * Construye de forma segura un ID de cliente a partir de su tipo y número. + * + * @param idType El tipo de identificación (ej. "V", "J"). + * @param idNumber El número de identificación. + * @return Un {@link Optional} con el ID concatenado si ambos parámetros son válidos (no nulos ni vacíos), + * o un {@code Optional.empty()} en caso contrario. + */ + public static Optional buildCustomerId(String idType, String idNumber) { + return Optional.ofNullable(idType) + .filter(type -> !type.isBlank()) + .flatMap(type -> Optional.ofNullable(idNumber) + .filter(number -> !number.isBlank()) + .map(number -> type + number)); + } + +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java b/rec-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java new file mode 100644 index 0000000..8ea4992 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java @@ -0,0 +1,78 @@ +package application.helper; + +import domain.dto.response.CustomerPositionStateResponse; +import domain.dto.response.CustomerResponse; +import domain.dto.response.StatusResponse; +import domain.model.BankingProduct; +import domain.model.ErrorCode; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.core.Response; + +import java.util.List; + +@ApplicationScoped +public class ResponseHelper { + + private final StatusDescriptionResolver statusDescriptionResolver; + + public ResponseHelper(StatusDescriptionResolver statusDescriptionResolver) { + this.statusDescriptionResolver = statusDescriptionResolver; + } + + public Response buildErrorResponse(String errorCodeKey, String requestId) { + ErrorCode errorCode = statusDescriptionResolver.getCode(errorCodeKey); + return this.buildErrorResponse(errorCode, requestId); + } + + /** + * Construye una respuesta de error estandarizada de forma genérica. + * @param errorCode Objeto con el código de error interno. + * @return Un objeto Response estándar de JAX-RS. + */ + public Response buildErrorResponse(ErrorCode errorCode, String requestId) { + + var statusResponse = StatusResponse.builder() + .status(errorCode.status()) + .statusCode(errorCode.statusCode()) + .message(errorCode.description()) + .traceId(requestId) + .build(); + + var response = new CustomerResponse(statusResponse); + + int httpStatus = errorCode.getHttpStatusCode(); + + return Response.status(httpStatus) + .entity(response) + .build(); + } + + /** + * Construye una respuesta de éxito (200) CON DATOS. + * @param customerAccounts La lista de productos (el cuerpo de la respuesta). + * @param successCode El ErrorCode "200" (para los detalles del status). + * @param requestId El ID de traza. + * @return Un objeto Response estándar de JAX-RS. + */ + public Response buildSuccessResponse(List customerAccounts, ErrorCode successCode, String requestId) { + + var status = StatusResponse.builder() + .status(successCode.status()) + .statusCode(successCode.statusCode()) + .message(successCode.description()) + .traceId(requestId) + .build(); + + var accounts = CustomerPositionStateResponse.builder() + .bankingProducts(customerAccounts) + .build(); + + var response = new CustomerResponse(status, accounts); + + return Response.status(successCode.getHttpStatusCode()) + .entity(response) + .build(); + } + +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java b/rec-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java new file mode 100644 index 0000000..b0669f6 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java @@ -0,0 +1,52 @@ +package application.usecase; + +import application.exception.GatewayServiceException; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.dto.request.CustomerRequest; +import domain.model.ExecutionResult; +import infrastructure.client.accounts.adapter.CustomerProductAdapter; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@ApplicationScoped +public class CustomerProductUseCase { + private final CustomerProductAdapter customerProductAdapter; + private final StatusDescriptionResolver statusDescriptionResolver; + + @Inject + public CustomerProductUseCase( + CustomerProductAdapter customerProductAdapter, + StatusDescriptionResolver statusDescriptionResolver + ) { + this.customerProductAdapter = customerProductAdapter; + this.statusDescriptionResolver = statusDescriptionResolver; + } + + public ExecutionResult getCustomerProduct( + CustomerRequest request, + String requestId, + String appId, + String customerReferenceFintechId, + String customerReferenceUser + ) { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: CustomerProductUseCase")); + try { + return customerProductAdapter.getCustomerProduct( + request, + requestId, + appId, + customerReferenceFintechId, + customerReferenceUser + ); + } catch (GatewayServiceException ex) { + log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio: " + ex.getMessage())); + var error = statusDescriptionResolver.getCode("503-URL"); + return ExecutionResult.failure(error); + } finally { + log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: CustomerProductUseCase")); + } + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java b/rec-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java new file mode 100644 index 0000000..c4f3951 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java @@ -0,0 +1,96 @@ +package domain.dto.request; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@RegisterForReflection +public class CustomerRequest { + + @Valid + @NotNull(message = "VDE01") + private CustomerPositionState customerPositionState; + + @Valid + @NotNull(message = "VDE01") + private Device device; + +// @Valid +// @NotNull(message = "VDE01") + private TokenAssignmentInstance tokenAssignmentInstance; + private String productReferenceSearch; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class CustomerPositionState { + + @Valid + @NotNull(message = "VDE01") + private CustomerReference customerReference; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @RegisterForReflection + public static class CustomerReference { + + /** + * Valida que sea una única letra del conjunto [V, E, P, G, J]. + */ + @NotBlank(message = "VDE01") + @Pattern(regexp = "^[VEPGJ]$", message = "VDE02") + private String customerIdType; + + /** + * Valida que sea numérico con un máximo de 9 dígitos. + */ + @NotBlank(message = "VDE01") + @Pattern(regexp = "^$|^[0-9]{4,9}$", message = "VDE02") + private String customerId; + + } + + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @RegisterForReflection + public static class Device { + @NotBlank(message = "VDE01") + private String deviceType; + + @NotBlank(message = "VDE01") + private String deviceDescription; + + @NotBlank(message = "VDE01") + private String deviceIp; + + @NotBlank(message = "VDE01") + private String deviceSessionReference; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @RegisterForReflection + public static class TokenAssignmentInstance { + + /** + * Este campo puede ser nulo, vacío ("") o un string de 8 dígitos. + */ + @Pattern(regexp = "^$|^[0-9]{8}$", message = "VDE02") + private String tokenIdentificationCode; + + } + +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java new file mode 100644 index 0000000..4a1a529 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java @@ -0,0 +1,16 @@ +package domain.dto.response; + +import domain.model.BankingProduct; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Value +@Builder +@RegisterForReflection +public class CustomerPositionStateResponse { + int record; + List bankingProducts; +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java new file mode 100644 index 0000000..8c9ffbb --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java @@ -0,0 +1,27 @@ +package domain.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@RegisterForReflection +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CustomerResponse { + StatusResponse statusResponse; + CustomerPositionStateResponse customerPositionState; + + public CustomerResponse(StatusResponse statusResponse, CustomerPositionStateResponse customerPositionState) { + this.statusResponse = statusResponse; + this.customerPositionState = customerPositionState; + } + + public CustomerResponse(StatusResponse statusResponse) { + this.statusResponse = statusResponse; + } + +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java b/rec-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java new file mode 100644 index 0000000..d228655 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java @@ -0,0 +1,15 @@ +package domain.dto.response; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +@RegisterForReflection +public class StatusResponse { + String status; + String statusCode; + String message; + String traceId; +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java b/rec-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java new file mode 100644 index 0000000..a8e2ba2 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java @@ -0,0 +1,19 @@ +package domain.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Builder; +import lombok.Value; + +import java.math.BigDecimal; + +@Value +@Builder +@RegisterForReflection +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BankingProduct { + String productNumber; + String productType; + BigDecimal balance; + String currency; +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java b/rec-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java new file mode 100644 index 0000000..76bc51c --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java @@ -0,0 +1,28 @@ +package domain.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.quarkus.runtime.annotations.RegisterForReflection; + +/** + * Representa un código de error de la aplicación. + */ +@RegisterForReflection +public record ErrorCode( + @JsonProperty("backend_code") String backendCode, + @JsonProperty("http_code") String httpCode, + @JsonProperty("status_code") String statusCode, + @JsonProperty("description") String description, + @JsonProperty("status") String status +) { + /** + * Convierte de forma segura el 'httpCode' a un código de estado HTTP numérico. + * @return El código de estado HTTP como un entero. + */ + public int getHttpStatusCode() { + try { + return Integer.parseInt(this.httpCode); + } catch (NumberFormatException e) { + return 500; + } + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java new file mode 100644 index 0000000..d46d1ba --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java @@ -0,0 +1,43 @@ +package domain.model; + +import domain.dto.response.CustomerResponse; + +public class ExecutionResult { + + private final CustomerResponse successResponse; + private final ErrorCode errorCode; + + // Constructor privado para éxito + private ExecutionResult(CustomerResponse successResponse) { + this.successResponse = successResponse; + this.errorCode = null; + } + + // Constructor privado para error + private ExecutionResult(ErrorCode errorCode) { + this.successResponse = null; + this.errorCode = errorCode; + } + + // --- Métodos de fábrica (Factory) --- + public static ExecutionResult success(CustomerResponse response) { + return new ExecutionResult(response); + } + + public static ExecutionResult failure(ErrorCode code) { + return new ExecutionResult(code); + } + + // --- Métodos de comprobación --- + public boolean isSuccess() { + return this.successResponse != null; + } + + public CustomerResponse getSuccessResponse() { + return successResponse; + } + + public ErrorCode getErrorCode() { + return errorCode; + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java~ b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java~ new file mode 100644 index 0000000..3ef2d42 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java~ @@ -0,0 +1,4 @@ +package domain.model; + +public class ExecutionResult { +} diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java b/rec-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java new file mode 100644 index 0000000..2df9cf1 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java @@ -0,0 +1,286 @@ +package infrastructure.adapter.input.rest; + +import application.helper.ResponseHelper; +import application.usecase.CustomerProductUseCase; +import com.banesco.common.infraestructure.helpers.JsonHelper; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import domain.dto.request.CustomerRequest; +import domain.dto.response.CustomerResponse; +import domain.dto.response.StatusResponse; +import infrastructure.context.TraceIdContext; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.ExampleObject; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameters; +import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + +@Slf4j +@Path("/customer") +public class CustomerResource { + + // --- DEPENDENCIAS INYECTADAS --- + private final TraceIdContext traceIdContext; + private final ResponseHelper responseHelper; + + // -- CASO DE USO + private final CustomerProductUseCase customerProductUseCase; + + public CustomerResource( + TraceIdContext traceIdContext, + ResponseHelper responseHelper, + CustomerProductUseCase customerProductUseCase + ) { + this.traceIdContext = traceIdContext; + this.responseHelper = responseHelper; + this.customerProductUseCase = customerProductUseCase; + } + + @POST + @Path("/position/evaluate") + @Operation( + summary = "Consulta de Productos", + description = "Orquesta a bus-evaluate-customer-product (BUS Consulta de Producto) para consultar las cuentas del cliente." + ) + @Parameters({ + @Parameter( + name = "requestId", + in = ParameterIn.HEADER, + description = "Identificador de la solicitud para la trazabilidad.", + schema = @Schema(type = SchemaType.STRING), + example = "7WCnvznd0CPrBZ0jF4af" + ), + @Parameter( + name = "appId", + required = true, + in = ParameterIn.HEADER, + description = "Identificador de la aplicación cliente para cargar una configuración de header específica.", + schema = @Schema(type = SchemaType.STRING), + example = "DANIAPP" + ) + }) + @RequestBody( + description = "Cuerpo de la solicitud con los datos necesarios para consultar las cuentas.", + content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = + @Schema(implementation = CustomerRequest.class), + examples = { + @ExampleObject( + name = "Solicitud Estándar de Búsqueda por Producto (TDD)", + summary = "Ejemplo de solicitud de productos (TDD) con éxito", + externalValue = "/openapi-examples/request/customer-product-search-success.json" + ), + @ExampleObject( + name = "Solicitud Estándar de Búsqueda por Producto sin Resultados (TDD)", + summary = "Ejemplo de solicitud de productos (TDD) sin resultados", + externalValue = "/openapi-examples/request/customer-product-search-wihtout-result.json" + ), + @ExampleObject( + name = "Solicitud Estándar", + summary = "Ejemplo de solicitud con éxito", + externalValue = "/openapi-examples/request/customer-product-success.json" + ), + @ExampleObject( + name = "Solicitud Estándar (Sin tokenAssignmentInstance)", + summary = "Ejemplo de solicitud con éxito (sin objeto para OTP)", + externalValue = "/openapi-examples/request/customer-product-success-without-token.json" + ), + @ExampleObject( + name = "Solicitud con Token", + summary = "Ejemplo de solicitud con token (éxito)", + externalValue = "/openapi-examples/request/customer-product-token-success.json" + ) + } + ) + ) + @APIResponses(value = { + @APIResponse( + responseCode = "200", + description = "Operación Exitosa. Devuelve la lista de productos del cliente.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CustomerResponse.class), + examples = { + @ExampleObject( + name = "Búsqueda por Producto", + summary = "Respuesta exitosa", + description = "Realiza busqueda de por tipo de producto (productReferenceSearch).", + externalValue = "/openapi-examples/response/customer-product-search-response.json" + ), + @ExampleObject( + name = "Búsqueda por Producto sin Resultados", + summary = "Respuesta exitosa", + description = "Realiza busqueda de por tipo de producto (productReferenceSearch) sin resultados.", + externalValue = "/openapi-examples/response/customer-product-search-response-without-results.json" + ), + @ExampleObject( + name = "Cuentas Enmascaradas", + summary = "Respuesta exitosa (Enmascaramiento activo)", + description = "La configuración de la app indica que 'encryptionData' es true.", + externalValue = "/openapi-examples/response/customer-product-response.json" + ), + @ExampleObject( + name = "Cuentas Reales", + summary = "Respuesta exitosa (Sin enmascaramiento)", + description = "La configuración de la app indica que 'encryptionData' es false.", + externalValue = "/openapi-examples/response/customer-product-response-unmasked.json" + ), + @ExampleObject( + name = "Sin Resultados", + summary = "Respuesta exitosa (Cliente sin productos)", + description = "La consulta se completó, pero el cliente no tiene productos que coincidan.", + externalValue = "/openapi-examples/response/customer-product-without-results.json" + ) + } + ) + ), + @APIResponse( + responseCode = "401", + description = "Error de Autorización o Perfil. El perfil del cliente no es válido o falló una validación de seguridad.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Perfil Inválido", + summary = "VRN01 - Perfil no válido", + externalValue = "/openapi-examples/response/invalid-profile.json" + ) + } + ) + ), + @APIResponse( + responseCode = "400", + description = "Datos de entrada inválidos, como un token OTP incorrecto o una configuración de app no encontrada.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Token Inválido", + summary = "VRN07 - OTP Inválido", + externalValue = "/openapi-examples/response/invalid-token.json" + ), + @ExampleObject( + name = "Configuración no encontrada", + summary = "VRN_CONFIG_NOT_FOUND", + externalValue = "/openapi-examples/response/config-not-found.json" + ) + } + ) + ), + @APIResponse( + responseCode = "404", + description = "Recurso no encontrado. Puede ser un usuario BOL, un login vacío.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CustomerResponse.class), + examples = { + @ExampleObject( + name = "Usuario no existe", + summary = "AUTH02 - Usuario no existe", + externalValue = "/openapi-examples/response/user-bol-not-exist.json" + ), + @ExampleObject( + name = "Usuario vacío", + summary = "AUTH01 - Login vacío", + externalValue = "/openapi-examples/response/user-not-exist.json" + ), + } + ) + ), + @APIResponse( + responseCode = "409", + description = "CONFLICTO.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Conflicto", + summary = "Conflicto (revisar)", + externalValue = "/openapi-examples/response/conflict.json" + ) + } + ) + ), + @APIResponse( + responseCode = "500", + description = "Uso Interno. Ocurrio un error inesperado.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Uso Interno", + summary = "Uso Interno (revisar)", + externalValue = "/openapi-examples/response/internal-use.json" + ) + } + ) + ), + @APIResponse( + responseCode = "503", + description = "Servicio no disponible. Ocurrió un error con una dependencia interna o el servicio está en mantenimiento.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = StatusResponse.class), + examples = { + @ExampleObject( + name = "Mantenimiento", + summary = "VRN04 - Servicio en mantenimiento", + externalValue = "/openapi-examples/response/service-unavailable.json" + ), + @ExampleObject( + name = "Error de Dependencia", + summary = "Error de CI", + externalValue = "/openapi-examples/response/customer-product-rest-client-error.json" + ) + } + ) + ) + }) + public Response evaluatePosition( + @HeaderParam("appId") String appId, + @HeaderParam("customerReferenceFintechId") String customerReferenceFintechId, + @HeaderParam("customerReferenceUser") String customerReferenceUser, + @Valid @NotNull(message = "VDE01") CustomerRequest request + ) { + final var requestId = traceIdContext.getTraceId(); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, appId, JsonHelper.getJsonFromObject(request))); + + var result = customerProductUseCase.getCustomerProduct( + request, + requestId, + appId, + customerReferenceFintechId, + customerReferenceUser + ); + + if (result.isSuccess()) { + var customerProducts = result.getSuccessResponse(); + log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de bus-evaluate-customer-product", customerProducts)); + + return Response.ok(customerProducts).build(); + + } else { + var errorCode = result.getErrorCode(); + log.warn(LoggerHelper.buildError(requestId, "Error de negocio manejado en Resource: " + errorCode.statusCode())); + + return responseHelper.buildErrorResponse(errorCode, requestId); + } + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java new file mode 100644 index 0000000..60d471b --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java @@ -0,0 +1,226 @@ +package infrastructure.client.accounts.adapter; + +import application.exception.GatewayServiceException; +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import com.fasterxml.jackson.databind.ObjectMapper; +import domain.dto.request.CustomerRequest; +import domain.dto.response.CustomerResponse; +import domain.model.ErrorCode; +import domain.model.ExecutionResult; +import infrastructure.client.accounts.api.CustomerAccountClient; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Slf4j +@ApplicationScoped +public class CustomerProductAdapter { + + private final CustomerAccountClient client; + private final ObjectMapper objectMapper; + private final StatusDescriptionResolver statusDescriptionResolver; + + public CustomerProductAdapter( + @RestClient CustomerAccountClient client, + ObjectMapper objectMapper, + StatusDescriptionResolver statusDescriptionResolver + ) { + this.client = client; + this.objectMapper = objectMapper; + this.statusDescriptionResolver = statusDescriptionResolver; + } + + public ExecutionResult getCustomerProduct( + CustomerRequest request, + String requestId, + String appId, + String customerReferenceFintechId, + String customerReferenceUser + ) { + try { + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de bus-evaluate-customer-product", request)); + + // Mock para cliente sin productos + if (request.getCustomerPositionState() != null + && request.getCustomerPositionState().getCustomerReference() != null + && "V".equals(request.getCustomerPositionState().getCustomerReference().getCustomerIdType()) + && "8678988".equals(request.getCustomerPositionState().getCustomerReference().getCustomerId())) { + + domain.dto.response.StatusResponse statusResponse = domain.dto.response.StatusResponse.builder() + .status("ok") + .statusCode("200") + .message("Cliente sin productos") + .traceId(requestId) + .build(); + + domain.dto.response.CustomerPositionStateResponse customerPositionState = + domain.dto.response.CustomerPositionStateResponse.builder() + .record(0) + .bankingProducts(java.util.Collections.emptyList()) + .build(); + + CustomerResponse customerResponse = new CustomerResponse(statusResponse, customerPositionState); + + return ExecutionResult.success(customerResponse); + } + + if (request.getProductReferenceSearch() != null && !request.getProductReferenceSearch().isEmpty()) { + // Mock para cliente con productos + domain.dto.response.StatusResponse statusResponse = domain.dto.response.StatusResponse.builder() + .status("ok") + .statusCode("200") + .message("Operacion Exitosa") + .traceId(requestId) + .build(); + + // Producto + domain.model.BankingProduct product = domain.model.BankingProduct.builder() + .productNumber("****-****-****-8514") + .productType("TDB") + // .balance(null) + // .currency(null) + .build(); + + java.util.List products = java.util.Collections.singletonList(product); + + // CustomerPositionStateResponse + domain.dto.response.CustomerPositionStateResponse customerPositionState = + domain.dto.response.CustomerPositionStateResponse.builder() + .record(1) + .bankingProducts(products) + .build(); + + // CustomerResponse + CustomerResponse customerResponse = new CustomerResponse(statusResponse, customerPositionState); + + return ExecutionResult.success(customerResponse); + } + + var response = client.readCustomerProducts( + requestId, + appId, + customerReferenceFintechId, + customerReferenceUser, + request + ); + + if (response == null) { + log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de bus-evaluate-customer-product")); + throw new GatewayServiceException("Respuesta invalida del servicio externo de bus-evaluate-customer-product"); + } + + return ExecutionResult.success(response); + + } catch (WebApplicationException ex) { + return handleHttpError(ex, requestId); + + } catch (RuntimeException ex) { + // Error de red, timeout, etc. + throw handleNetworkError(ex, requestId); + } + } + + /** + * Orquesta el manejo de errores HTTP (WebApplicationException). + */ + private ExecutionResult handleHttpError(WebApplicationException ex, String requestId) { + var errorResponse = ex.getResponse(); + int status = errorResponse.getStatus(); + String errorBody = readErrorBody(errorResponse); + + // Si es un error de infraestructura (HTML), lanza excepción. + if (isInfrastructureError(errorResponse)) { + String htmlError = String.format("Error HTTP %s del servicio. Se recibio una respuesta HTML no esperada.", status); + log.error(LoggerHelper.buildError(requestId, htmlError)); + throw new GatewayServiceException(htmlError); + } + + // Si no es HTML, debe ser un error de negocio (JSON). Intentamos parsearlo. + try { + return parseBusinessError(errorBody, status, requestId); + } catch (Exception parseEx) { + // Si falla el parseo, es un error de infraestructura (contrato JSON roto). + var rawError = String.format("Error HTTP no manejado (body no parseable). Status: %s. Body: %s", + status, errorBody.substring(0, Math.min(errorBody.length(), 1000))); + log.error(LoggerHelper.buildError(requestId, rawError), parseEx); + throw new GatewayServiceException(rawError, parseEx); + } + } + + /** + * Parsea un error de negocio (JSON) y lo convierte en un ExecutionResult. + */ + private ExecutionResult parseBusinessError(String errorBody, int httpStatus, String requestId) throws com.fasterxml.jackson.core.JsonProcessingException { + var errorDto = objectMapper.readValue(errorBody, CustomerResponse.class); + + if (errorDto != null && errorDto.getStatusResponse() != null) { + var statusCode = errorDto.getStatusResponse().getStatusCode(); + var message = errorDto.getStatusResponse().getMessage(); + + var bizError = String.format("Error de negocio (HTTP %s) del servicio. Code: %s, Message: %s", httpStatus, statusCode, message); + log.warn(LoggerHelper.buildError(requestId, bizError)); + + var error = resolveErrorCode(statusCode, message, httpStatus, requestId); + return ExecutionResult.failure(error); + } + + // Si el JSON es válido, pero incompleto (ej. statusResponse: null), es un error de contrato + log.error(LoggerHelper.buildError(requestId, "Error de contrato: El JSON de error no contiene 'statusResponse'")); + throw new GatewayServiceException("Error de contrato: JSON de error invalido"); + } + + /** + * Busca el ErrorCode o crea uno de fallback. + */ + private ErrorCode resolveErrorCode(String statusCode, String message, int httpStatus, String requestId) { + var error = statusDescriptionResolver.getCode(statusCode); + if (error == null) { + log.warn("Codigo de error '{}' no encontrado en StatusDescriptionResolver. Usando valores del body.", statusCode); + error = new ErrorCode( + statusCode, + String.valueOf(httpStatus), + statusCode, + message, + "error" + ); + } + return error; + } + + /** + * Lee de forma segura el body de una respuesta de error. + */ + private String readErrorBody(Response errorResponse) { + if (!errorResponse.hasEntity()) { + return "No body"; + } + try { + return errorResponse.readEntity(String.class); + } catch (Exception e) { + log.error("No se pudo leer el stream del body de error: {}", e.getMessage()); + return "Error al leer stream"; + } + } + + /** + * Comprueba si la respuesta es un error de infraestructura (ej. HTML). + */ + private boolean isInfrastructureError(Response errorResponse) { + var mediaType = errorResponse.getMediaType(); + return mediaType != null && mediaType.toString().contains("text/html"); + } + + /** + * Maneja errores de red/comunicación (RuntimeException). + */ + private GatewayServiceException handleNetworkError(RuntimeException ex, String requestId) { + log.error(LoggerHelper.buildError( + requestId, + String.format("Error de comunicacion con el servicio: %s", ex.getMessage()) + ), ex); + return new GatewayServiceException("No se pudo comunicar con el servicio externo de bus-evaluate-customer-product.", ex); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java new file mode 100644 index 0000000..c66fc11 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java @@ -0,0 +1,22 @@ +package infrastructure.client.accounts.api; + +import domain.dto.request.CustomerRequest; +import domain.dto.response.CustomerResponse; +import infrastructure.interceptor.LoggingFilter; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "customer-accounts") +@RegisterProvider(LoggingFilter.class) +public interface CustomerAccountClient { + @POST + CustomerResponse readCustomerProducts( + @HeaderParam("requestId") String requestId, + @HeaderParam("appId") String appId, + @HeaderParam("customerReferenceFintechId") String customerReferenceFintechId, + @HeaderParam("customerReferenceUser") String customerReferenceUser, + CustomerRequest request + ); +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java b/rec-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java new file mode 100644 index 0000000..e7f545b --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java @@ -0,0 +1,82 @@ +package infrastructure.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import domain.model.ErrorCode; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +@Getter +@ApplicationScoped +public class AppConfig { + + @Inject + ObjectMapper objectMapper; + + @ConfigProperty(name = "app.response-json") + private Optional responseJsonString; + + @ConfigProperty(name = "app.print-response", defaultValue = "false") + private Boolean printResponse; + + private List errorCodesList; + + @Getter + private Map errorCodeMap; + + @PostConstruct + public void init() { + log.info("Inicializando configuracion de la aplicacion..."); + + if (responseJsonString.isPresent() && !responseJsonString.get().isBlank()) { + try { + this.errorCodesList = objectMapper.readValue(responseJsonString.get(), new TypeReference>() {}); + this.errorCodeMap = this.errorCodesList.stream() + .collect(Collectors.toMap( + ErrorCode::backendCode, + Function.identity(), + (existing, replacement) -> existing + )); + log.info("Se cargaron {} codigos de error en el mapa.", this.errorCodeMap.size()); + } catch (JsonProcessingException e) { + log.error("Error fatal al parsear la propiedad 'app.response-json'.", e); + this.errorCodesList = Collections.emptyList(); + this.errorCodeMap = Collections.emptyMap(); + } + } else { + log.warn("La propiedad 'app.response-json' no esta definida o esta vacia."); + this.errorCodesList = Collections.emptyList(); + this.errorCodeMap = Collections.emptyMap(); + } + + log.info("--- Validando propiedades de configuracion ---"); + + if (errorCodesList.isEmpty()) { + log.warn("JSON de respuesta no configurado o vacio. El resolvedor de codigos podria no operar correctamente."); + } else { + log.info("Configuracion de 'app.response-json' validada."); + } + + log.info("app.print-response configurado: {}", printResponse); + + log.info("Configuracion completada."); + } + + public List getAppJson() { + return errorCodesList; + } + +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java b/rec-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java new file mode 100644 index 0000000..369f104 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java @@ -0,0 +1,11 @@ +package infrastructure.config; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "app.register-security") +public interface RegisterSecurityConfig { + String eventCod(); + String bankCod(); + String curCod(); + String sp(); +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java b/rec-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java new file mode 100644 index 0000000..530ff19 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java @@ -0,0 +1,12 @@ +package infrastructure.context; + +import jakarta.enterprise.context.RequestScoped; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@RequestScoped +public class TraceIdContext { + private String traceId; +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java new file mode 100644 index 0000000..9c92ada --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java @@ -0,0 +1,97 @@ +package infrastructure.exception; + +import domain.dto.response.CustomerResponse; +import domain.dto.response.StatusResponse; +import infrastructure.context.TraceIdContext; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.inject.Inject; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.jboss.logging.Logger; + +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Provider +public class ConstraintViolationExceptionHandler implements ExceptionMapper { + + private static final Logger LOG = Logger.getLogger(ConstraintViolationExceptionHandler.class); + + @Inject + TraceIdContext traceIdContext; + + @Inject + StatusDescriptionResolver statusDescriptionResolver; + + @Override + public Response toResponse(ConstraintViolationException exception) { + var traceId = traceIdContext.getTraceId(); + var violations = exception.getConstraintViolations(); + + if (violations.isEmpty()) { + return buildGenericErrorResponse(traceId, exception); + } + + // Se busca la violación con la mayor prioridad según la lista definida. + var highestPriorityViolation = findHighestPriorityViolation(violations); + + // Extraer el backendCode directamente del mensaje de la anotación. + var backendCode = highestPriorityViolation.getMessage(); + var fieldPath = highestPriorityViolation.getPropertyPath().toString(); + + // Obtenemos el nombre simple del campo para el mensaje. + var jsonProperty = fieldPath.substring(fieldPath.lastIndexOf('.') + 1); + + LOG.warnf("[traceId: %s] Error de validacion en el campo '%s'. Mensaje/Codigo: '%s'", traceId, fieldPath, backendCode); + + // Obtener los detalles del error. + var resolvedCode = statusDescriptionResolver.getCode(backendCode); + + var statusResponse = StatusResponse.builder() + .status("error") + .statusCode(resolvedCode.statusCode()) + .message(resolvedCode.description() + ": " + jsonProperty) + .traceId(traceId) + .build(); + + var response = new CustomerResponse(statusResponse); + + return Response.status(Response.Status.BAD_REQUEST) + .entity(response) + .build(); + } + + private ConstraintViolation findHighestPriorityViolation(Set> violations) { + return violations.stream().findFirst().get(); + } + + /** + * Construye una respuesta de error genérica en caso de que no se encuentren violaciones específicas. + */ + private Response buildGenericErrorResponse(String traceId, ConstraintViolationException exception) { + var detailsForLog = exception.getConstraintViolations() + .stream() + .map(cv -> cv.getPropertyPath() + ": " + cv.getMessage()) + .collect(Collectors.joining("; ")); + + LOG.warnf("[traceId: %s] Error de validación genérico detectado, %s", traceId, detailsForLog); + + var statusResponse = StatusResponse.builder() + .status("error") + .statusCode("VDE-400") + .message("Error en los datos de entrada. Verifique la información enviada") + .traceId(traceId) + .build(); + + var response = new CustomerResponse(statusResponse); + + return Response.status(Response.Status.BAD_REQUEST) + .entity(response) + .build(); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..423c1d7 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java @@ -0,0 +1,84 @@ +package infrastructure.exception; + +import application.exception.GatewayServiceException; +import com.banesco.common.application.exception.BanBackendException; +import domain.dto.response.CustomerResponse; +import domain.model.ErrorCode; +import infrastructure.context.TraceIdContext; +import infrastructure.service.StatusDescriptionResolver; +import jakarta.inject.Inject; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.NotAllowedException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.NotSupportedException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Provider +public class GlobalExceptionHandler implements ExceptionMapper { + + @Inject + TraceIdContext traceIdContext; + + @Inject + StatusDescriptionResolver statusDescriptionResolver; + + @Override + public Response toResponse(Throwable exception) { + final var requestId = traceIdContext.getTraceId(); + ErrorCode resolvedCode; + + var exceptionClassName = exception.getClass().getName(); + + if (exception instanceof BanBackendException bbe) { + resolvedCode = statusDescriptionResolver.getCode(bbe.getBackendCode()); + if (resolvedCode == null) { + resolvedCode = statusDescriptionResolver.getCode("500"); + } + } else if (exceptionClassName.equals(GatewayServiceException.class.getName()) || + exceptionClassName.equals(application.exception.ServiceCommunicationException.class.getName())) { + resolvedCode = statusDescriptionResolver.getCode("503-URL"); + + } else if (exception instanceof NotSupportedException) { + resolvedCode = statusDescriptionResolver.getCode("VDE01"); + + } else if (exception instanceof NotFoundException) { + resolvedCode = statusDescriptionResolver.getCode("ERR-404"); + + } else if (exception instanceof NotAllowedException) { + resolvedCode = statusDescriptionResolver.getCode("405"); + + } else if (isClientError(exception)) { + resolvedCode = statusDescriptionResolver.getCode("ERR-400"); + + } else { + // Default para cualquier otra excepción no controlada + log.error("[ERROR: {}] Error de backend no manejado que llego al GlobalExceptionHandler", requestId, exception); + resolvedCode = statusDescriptionResolver.getCode("500"); + } + + var statusResponse = domain.dto.response.StatusResponse.builder() + .status("error") + .statusCode(resolvedCode.statusCode()) + .message(resolvedCode.description()) + .traceId(requestId) + .build(); + + var response = new CustomerResponse(statusResponse); + + int httpStatus = Integer.parseInt(resolvedCode.httpCode().split("-")[0]); + + return Response.status(httpStatus) + .entity(response) + .build(); + } + + private boolean isClientError(Throwable throwable) { + return throwable instanceof BadRequestException || + throwable instanceof com.fasterxml.jackson.core.JsonProcessingException || + throwable instanceof NullPointerException; + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java new file mode 100644 index 0000000..d46bdb5 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java @@ -0,0 +1,43 @@ +package infrastructure.filter; + +import com.banesco.common.infraestructure.helpers.LoggerHelper; +import infrastructure.config.AppConfig; +import infrastructure.context.TraceIdContext; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; + +import java.util.stream.Collectors; + + +@Slf4j +@Provider +@Priority(200) +@ApplicationScoped +public class HeadersFilter implements ContainerRequestFilter { + + @Inject + AppConfig appConfig; + + @Inject + TraceIdContext traceIdContext; + + @Override + public void filter(ContainerRequestContext requestContext) { + var requestId = traceIdContext.getTraceId(); + + var multiMappedHeaders = requestContext.getHeaders(); + + var headersMap = multiMappedHeaders.keySet().stream() + .collect(Collectors.toMap( + key -> key, + multiMappedHeaders::getFirst + )); + + log.info(LoggerHelper.buildInfoPrivateRequest(requestId, LoggerHelper.formatHeaders(headersMap))); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java new file mode 100644 index 0000000..1b7fd8f --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java @@ -0,0 +1,30 @@ +package infrastructure.filter; + +import com.banesco.common.infraestructure.helpers.RequestHelper; +import infrastructure.context.TraceIdContext; +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Provider +@PreMatching // run before any other filter +@Priority(100) +public class TraceIdFilter implements ContainerRequestFilter { + + @Inject + TraceIdContext traceIdContext; + + @Override + public void filter(ContainerRequestContext requestContext) { + var headerId = requestContext.getHeaderString("requestId"); + + var finalTraceId = (headerId != null && !headerId.isBlank()) ? headerId : RequestHelper.randomAlphanumeric(20); + + traceIdContext.setTraceId(finalTraceId); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java new file mode 100644 index 0000000..7016427 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java @@ -0,0 +1,36 @@ +package infrastructure.healthcheck; + +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Liveness; + +@Slf4j +@Liveness +@ApplicationScoped +public class ApplicationLivenessCheck implements HealthCheck { + + private final String applicationName; + private final String applicationVersion; + + + public ApplicationLivenessCheck( + @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName, + @ConfigProperty(name = "quarkus.application.version", defaultValue = "unknown") String applicationVersion) { + this.applicationName = applicationName; + this.applicationVersion = applicationVersion; + } + + @Override + public HealthCheckResponse call() { + log.debug("Ejecutando liveness health check: {}", System.currentTimeMillis()); + return HealthCheckResponse.named("API") + .up() + .withData("application", applicationName) + .withData("version", applicationVersion) + .withData("timestamp", System.currentTimeMillis()) + .build(); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java new file mode 100644 index 0000000..2a0636b --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java @@ -0,0 +1,38 @@ +package infrastructure.healthcheck; + +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Startup; + +/** + * Startup Check: Verifica que el contexto de la aplicación (CDI) + * se ha inicializado correctamente y las configuraciones básicas están disponibles. + * Este chequeo le dice a Kubernetes "El arranque interno finalizó", + * permitiendo que los chequeos de Liveness y Readiness tomen el control. + */ +@Slf4j +@Startup +@ApplicationScoped +public class ApplicationStartupCheck implements HealthCheck { + + private final String applicationName; + + public ApplicationStartupCheck( + @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName) { + this.applicationName = applicationName; + } + + @Override + public HealthCheckResponse call() { + log.info("Ejecutando startup health check para {}...", applicationName); + + return HealthCheckResponse.named("Application Startup") + .up() + .withData("application", applicationName) + .withData("status", "Contexto de aplicación (CDI) iniciado.") + .build(); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java new file mode 100644 index 0000000..778a2f7 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java @@ -0,0 +1,31 @@ +package infrastructure.interceptor; + +import infrastructure.context.TraceIdContext; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.ext.Provider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +@Provider +public class LoggingFilter implements ClientRequestFilter { + private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class); + + private final TraceIdContext traceIdContext; + + public LoggingFilter(TraceIdContext traceIdContext) { + this.traceIdContext = traceIdContext; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + var requestId = traceIdContext.getTraceId(); + + LOG.info("[{}] Peticion REST a: {} {}", + requestId, + requestContext.getMethod(), + requestContext.getUri()); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java b/rec-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java new file mode 100644 index 0000000..15d0606 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java @@ -0,0 +1,45 @@ +package infrastructure.service; + +import domain.model.ErrorCode; +import infrastructure.config.AppConfig; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.util.Optional; + +@ApplicationScoped +public class StatusDescriptionResolver { + + private final AppConfig appConfig; + private final ErrorCode genericError; + + @Inject + public StatusDescriptionResolver(AppConfig appConfig) { + this.appConfig = appConfig; + // Definimos un código de error genérico por si no se encuentra el que buscamos + this.genericError = new ErrorCode("409", "CONFLICT", "409", "CONFLICT", "error"); + } + + /** + * Busca un ErrorCode por su backend_code. + * Si no lo encuentra, devuelve un ErrorCode genérico de "Error Interno". + * + * @param backendCode El código a buscar (ej. "200", "VDE01", "404"). + * @return El ErrorCode encontrado o uno genérico. Nunca es nulo. + */ + public ErrorCode getCode(String backendCode) { + // Usamos Optional.ofNullable para manejar el caso en que el código no exista en el mapa + return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode)) + .orElse(genericError); + } + + /** + * Alternativa manejar el caso "no encontrado" manualmente. + * + * @param backendCode El código a buscar. + * @return Un Optional que estará vacío si no se encuentra el código. + */ + public Optional findCode(String backendCode) { + return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode)); + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png new file mode 100644 index 0000000..9e7463a Binary files /dev/null and b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png differ diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png new file mode 100644 index 0000000..8e1b25d Binary files /dev/null and b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png differ diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000..6e15ce1 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,28 @@ + + + + + + + API Banesco - rec-evaluate-customer-product + + + + Logo Banesco + + \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html~ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html~ new file mode 100644 index 0000000..03837dd --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html~ @@ -0,0 +1,28 @@ + + + + + + + API Banesco - Pública Consulta de Producto BIAN + + + + Logo Banesco + + \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-success.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-success.json new file mode 100644 index 0000000..16f50a6 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-success.json @@ -0,0 +1,15 @@ +{ + "customerPositionState": { + "customerReference": { + "customerIdType": "J", + "customerId": "000004293" + } + }, + "device": { + "deviceType": "Mobile", + "deviceDescription": "Xiaomi Note 11 PRO", + "deviceIp": "127.0.0.1", + "deviceSessionReference": "prueba" + }, + "productReferenceSearch": "DEBITCARD" +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-wihtout-result.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-wihtout-result.json new file mode 100644 index 0000000..a3ebb8b --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-wihtout-result.json @@ -0,0 +1,15 @@ +{ + "customerPositionState": { + "customerReference": { + "customerIdType": "V", + "customerId": "8678988" + } + }, + "device": { + "deviceType": "Mobile", + "deviceDescription": "Xiaomi Note 11 PRO", + "deviceIp": "127.0.0.1", + "deviceSessionReference": "prueba" + }, + "productReferenceSearch": "DEBITCARD" +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success-without-token.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success-without-token.json new file mode 100644 index 0000000..28bb7ef --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success-without-token.json @@ -0,0 +1,14 @@ +{ + "customerPositionState": { + "customerReference": { + "customerIdType": "J", + "customerId": "000004293" + } + }, + "device": { + "deviceType": "Mobile", + "deviceDescription": "Xiaomi Note 11 PRO", + "deviceIp": "127.0.0.1", + "deviceSessionReference": "prueba" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json new file mode 100644 index 0000000..71a4f26 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json @@ -0,0 +1,17 @@ +{ + "customerPositionState": { + "customerReference": { + "customerIdType": "J", + "customerId": "000004293" + } + }, + "device": { + "deviceType": "Mobile", + "deviceDescription": "Xiaomi Note 11 PRO", + "deviceIp": "127.0.0.1", + "deviceSessionReference": "prueba" + }, + "tokenAssignmentInstance": { + "tokenIdentificationCode": "" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json new file mode 100644 index 0000000..a90178b --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json @@ -0,0 +1,17 @@ +{ + "customerPositionState": { + "customerReference": { + "customerIdType": "J", + "customerId": "000004293" + } + }, + "device": { + "deviceType": "Mobile", + "deviceDescription": "Xiaomi Note 11 PRO", + "deviceIp": "127.0.0.1", + "deviceSessionReference": "prueba" + }, + "tokenAssignmentInstance": { + "tokenIdentificationCode": "12345678" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json new file mode 100644 index 0000000..7f92dbf --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json @@ -0,0 +1,85 @@ +{ + "appName": "BANCA_MOVIL", + "apiName": "bus-evaluate-customer-product", + "type": "external", + "event": "CPROD", + "encryptionData": true, + "configuration": { + "validationRules": { + "serviceStatus": false, + "getUserLogin": false, + "encodeValue": false, + "validateOtp": false, + "validateProfile": false + }, + "aliases": { + "V": "V", + "E": "V", + "J": "J", + "G": "J", + "C": "J" + }, + "configuration": { + "all": { + "cacheable": true, + "bankCode": "01", + "currencyCode": "BS", + "status": "A", + "statusMe": "", + "productCv": "", + "product": "", + "channel": "", + "service": "", + "operator": "", + "phone": "", + "affiliationStatus": "", + "counterpartyAccount": "", + "holder": "", + "codProduct": "", + "statusOperation": "", + "statusRestriction": "", + "statusOperationRestriction": "" + }, + "BANCA_MOVIL_V": { + "cacheable": true, + "bankCode": "", + "currencyCode": "", + "status": "ACTBSUSD", + "statusMe": "", + "productCv": "CVFL", + "product": "", + "channel": "", + "service": "", + "operator": "", + "phone": "", + "affiliationStatus": "", + "counterpartyAccount": "", + "holder": "TIT", + "codProduct": "", + "statusOperation": "", + "statusRestriction": "", + "statusOperationRestriction": "" + }, + "BANCA_MOVIL_J": { + "cacheable": true, + "bankCode": "", + "currencyCode": "", + "status": "ACTBSUSD", + "statusMe": "", + "productCv": "CVFL", + "product": "", + "channel": "APP", + "service": "P2P", + "operator": "", + "phone": "", + "affiliationStatus": "A", + "counterpartyAccount": "PAG", + "holder": "TIT", + "codProduct": "", + "statusOperation": "", + "statusRestriction": "", + "statusOperationRestriction": "" + } + } + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json new file mode 100644 index 0000000..1b26bf5 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "400", + "message": "Configuracion no encontrada", + "traceId": "YxwjZEbfHuYc3wG4jDdN" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json new file mode 100644 index 0000000..d42706e --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "409", + "message": "CONFLICT", + "traceId": "b81LdZyGXYCCcLvyaQdz" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json new file mode 100644 index 0000000..abe8d5d --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json @@ -0,0 +1,25 @@ +{ + "statusResponse": { + "status": "ok", + "statusCode": "200", + "message": "Operacion Exitosa", + "traceId": "U2VzaLNEEPeX9ODZiRnC" + }, + "customerPositionState": { + "record": 2, + "bankingProducts": [ + { + "productNumber": "01340552220001000249", + "productType": "DDA", + "balance": 0.84, + "currency": "BS " + }, + { + "productNumber": "01341740730001281663", + "productType": "DDA", + "balance": 0.00, + "currency": "USD" + } + ] + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json new file mode 100644 index 0000000..29e755e --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json @@ -0,0 +1,25 @@ +{ + "statusResponse": { + "status": "ok", + "statusCode": "200", + "message": "Operacion Exitosa", + "traceId": "SKeIGjD1lWmJD992LfKZ" + }, + "customerPositionState": { + "record": 2, + "bankingProducts": [ + { + "productNumber": "0134************0249", + "productType": "DDA", + "balance": 0.84, + "currency": "BS " + }, + { + "productNumber": "********************", + "productType": "DDA", + "balance": 0.00, + "currency": "USD" + } + ] + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json new file mode 100644 index 0000000..881be26 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "503", + "message": "Incidencia con la resolución de CI", + "traceId": "qV3cYzb5SNIGKbozmF6T" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-search-response.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-search-response.json new file mode 100644 index 0000000..51a8f6b --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-search-response.json @@ -0,0 +1,17 @@ +{ + "statusResponse": { + "status": "ok", + "statusCode": "200", + "message": "Operacion Exitosa", + "traceId": "fJfPYbDnweXTRrcQHyN0" + }, + "customerPositionState": { + "record": 1, + "bankingProducts": [ + { + "productNumber": "****-****-****-8514", + "productType": "TDB" + } + ] + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json new file mode 100644 index 0000000..39740a3 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json @@ -0,0 +1,12 @@ +{ + "statusResponse": { + "status": "ok", + "statusCode": "200", + "message": "Cliente sin productos", + "traceId": "BOBfbbCpAR3ElrbEPFMl" + }, + "customerPositionState": { + "record": 0, + "bankingProducts": [] + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json new file mode 100644 index 0000000..70149a3 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "500", + "message": "Uso interno", + "traceId": "oHsLZM4xSVVC8ZoU6yDF" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json new file mode 100644 index 0000000..cda132e --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "401", + "message": "VRN01 - Fallo en la validación del perfil del cliente", + "traceId": "gLoRYYHkZONFOcntMvfn" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json new file mode 100644 index 0000000..cf5098d --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "VRN07", + "message": "VRN07 – Codigo de OTP Invalido", + "traceId": "xJPt6yoN6LA8QJxJpGC6" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json new file mode 100644 index 0000000..af8ed48 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "VRN04", + "message": "Servicio en horario de mantenimiento", + "traceId": "xif7wH0xUbIB3e6cHcr3" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json new file mode 100644 index 0000000..331e8b7 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "AUTH02", + "message": "Usuario no existe", + "traceId": "uAWXR6yNPa6IlQ51FOAU" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json new file mode 100644 index 0000000..ecb3a78 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json @@ -0,0 +1,8 @@ +{ + "statusResponse": { + "status": "error", + "statusCode": "AUTH01", + "message": "Usuario no existe", + "traceId": "jptc0JT9mh8ioDhZeZfa" + } +} \ No newline at end of file diff --git a/rec-evaluate-customer-product/src/main/resources/application.yml b/rec-evaluate-customer-product/src/main/resources/application.yml new file mode 100644 index 0000000..6e9a98b --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/application.yml @@ -0,0 +1,149 @@ +quarkus: + log: + level: INFO + http: + port: 8080 + idle-timeout: 30s + non-application-root-path: /actuator + application: + name: rec-evaluate-customer-product + version: 1.0.0 + smallrye-openapi: + path: /openapi + enable: true + info-title: rec-evaluate-customer-product + info-version: 1.0.0 + info-description: 'API Rec evaluate customer products' + swagger-ui: + path: /swagger-ui + always-include: true + health: + enabled: false + rest-client: + customer-accounts: + url: http://bus-evaluate-customer-product-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/position/evaluate + scope: jakarta.inject.Singleton +app: + appId: REC_EVALUATE_CUSTOMER_PRODUCT + response-json: | + [ + { + "backend_code": "default", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN_CONFIG_NOT_FOUND", + "http_code": "400", + "status_code": "400", + "description": "Configuracion no encontrada", + "status": "error" + }, + { + "backend_code": "405", + "http_code": "405", + "status_code": "405", + "description": "Metodo no permitido", + "status": "error" + }, + { + "backend_code": "200", + "http_code": "200", + "status_code": "200", + "description": "Operacion exitosa", + "status": "ok" + }, + { + "backend_code": "204", + "http_code": "200", + "status_code": "204", + "description": "Cliente sin productos", + "status": "ok" + }, + { + "backend_code": "503-URL", + "http_code": "503", + "status_code": "503", + "description": "Incidencia con la resolución de CI", + "status": "error" + }, + { + "backend_code": "500", + "http_code": "503", + "status_code": "500", + "description": "Uso interno", + "status": "error" + }, + { + "backend_code": "406", + "http_code": "406", + "status_code": "406", + "description": "Respuesta no aceptable", + "status": "error" + }, + { + "backend_code": "VDE01", + "http_code": "400", + "status_code": "VDE01", + "description": "Error en dato de entrada obligatorio", + "status": "error" + }, + { + "backend_code": "VDE02", + "http_code": "400", + "status_code": "VDE02", + "description": "Error en valor permitido para campo", + "status": "error" + }, + { + "backend_code": "VDE03", + "http_code": "400", + "status_code": "VDE03", + "description": "Longitud no permitida para campo", + "status": "error" + }, + { + "backend_code": "VRN01", + "http_code": "401", + "status_code": "401", + "description": "VRN01 - Fallo en la validación del perfil del cliente", + "status": "error" + }, + { + "backend_code": "VRN04", + "http_code": "503", + "status_code": "VRN04", + "description": "Servicio en horario de mantenimiento", + "status": "error" + }, + { + "backend_code": "VRN07", + "http_code": "401", + "status_code": "VRN07", + "description": "VRN07 – Codigo de OTP Invalido", + "status": "error" + }, + { + "backend_code": "409", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN19", + "http_code": "401", + "status_code": "AUTH01", + "description": "Usuario no existe", + "status": "error" + }, + { + "backend_code": "VRN20", + "http_code": "401", + "status_code": "AUTH02", + "description": "Usuario no existe", + "status": "error" + } + ] diff --git a/rec-evaluate-customer-product/src/main/resources/configmap.yaml b/rec-evaluate-customer-product/src/main/resources/configmap.yaml new file mode 100644 index 0000000..1b4b862 --- /dev/null +++ b/rec-evaluate-customer-product/src/main/resources/configmap.yaml @@ -0,0 +1,173 @@ +# =================================================================== +# CONFIGURACIÓN GENERAL DE QUARKUS +# =================================================================== + +# Nombre de la aplicación. +quarkus.application.name: rec-evaluate-customer-product +# Versión de la aplicación. +quarkus.application.version: 1.0.0 +# Puerto HTTP en el que se ejecutará la aplicación. +quarkus.http.port: 8080 +# Tiempo máximo de inactividad antes de que el servidor cierre una conexión HTTP. +quarkus.http.idle-timeout: 30s +# Ruta raíz para los endpoints que no son de la aplicación (ej. /health, /metrics). +quarkus.http.non-application-root-path: /actuator + +# =================================================================== +# DOCUMENTACIÓN DE API (OPENAPI & SWAGGER UI) +# =================================================================== + +# Habilita la generación de la especificación OpenAPI. +quarkus.smallrye-openapi.enable: true +# Ruta donde se expondrá la especificación OpenAPI (formato JSON/YAML). +quarkus.smallrye-openapi.path: /openapi +# Título que se mostrará en la documentación de la API. +quarkus.smallrye-openapi.info-title: rec-evaluate-customer-product +# Versión que se mostrará en la documentación de la API. +quarkus.smallrye-openapi.info-version: 1.0.0 +# Descripción general de la API para la documentación. +quarkus.smallrye-openapi.info-description: API Rec evaluate customer accounts + +# Habilita la interfaz de usuario de Swagger. +quarkus.swagger-ui.always-include: true +# Ruta donde estará disponible la interfaz de Swagger UI. +quarkus.swagger-ui.path: /swagger-ui + +# =================================================================== +# CONFIGURACIÓN DE CLIENTES REST +# =================================================================== + +# --- Cliente para obtener las cuentas de un cliente desde DB2. +quarkus.rest-client.customer-accounts.url: http://bus-evaluate-customer-product-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/position/evaluate +quarkus.rest-client.customer-accounts.scope: jakarta.inject.Singleton + +# =================================================================== +# PROPIEDADES ESPECÍFICAS DE LA APLICACIÓN +# =================================================================== + +# Identificador único de la aplicación. +app.appId: REC_EVALUATE_CUSTOMER_PRODUCTS + +# --- Mapeo centralizado de códigos de respuesta de la aplicación --- +app.response-json: | + [ + { + "backend_code": "default", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN_CONFIG_NOT_FOUND", + "http_code": "400", + "status_code": "400", + "description": "Configuracion no encontrada", + "status": "error" + }, + { + "backend_code": "405", + "http_code": "405", + "status_code": "405", + "description": "Metodo no permitido", + "status": "error" + }, + { + "backend_code": "200", + "http_code": "200", + "status_code": "200", + "description": "Operacion exitosa", + "status": "ok" + }, + { + "backend_code": "204", + "http_code": "200", + "status_code": "204", + "description": "Cliente sin productos", + "status": "ok" + }, + { + "backend_code": "503-URL", + "http_code": "503", + "status_code": "503", + "description": "Incidencia con la resolución de CI", + "status": "error" + }, + { + "backend_code": "500", + "http_code": "503", + "status_code": "500", + "description": "Uso interno", + "status": "error" + }, + { + "backend_code": "406", + "http_code": "406", + "status_code": "406", + "description": "Respuesta no aceptable", + "status": "error" + }, + { + "backend_code": "VDE01", + "http_code": "400", + "status_code": "VDE01", + "description": "Error en dato de entrada obligatorio", + "status": "error" + }, + { + "backend_code": "VDE02", + "http_code": "400", + "status_code": "VDE02", + "description": "Error en valor permitido para campo", + "status": "error" + }, + { + "backend_code": "VDE03", + "http_code": "400", + "status_code": "VDE03", + "description": "Longitud no permitida para campo", + "status": "error" + }, + { + "backend_code": "VRN01", + "http_code": "401", + "status_code": "401", + "description": "VRN01 - Fallo en la validación del perfil del cliente", + "status": "error" + }, + { + "backend_code": "VRN04", + "http_code": "503", + "status_code": "VRN04", + "description": "Servicio en horario de mantenimiento", + "status": "error" + }, + { + "backend_code": "VRN07", + "http_code": "401", + "status_code": "VRN07", + "description": "VRN07 – Codigo de OTP Invalido", + "status": "error" + }, + { + "backend_code": "409", + "http_code": "409", + "status_code": "CONFLICT", + "description": "Conflicto", + "status": "error" + }, + { + "backend_code": "VRN19", + "http_code": "401", + "status_code": "AUTH01", + "description": "Usuario no existe", + "status": "error" + }, + { + "backend_code": "VRN20", + "http_code": "401", + "status_code": "AUTH02", + "description": "Usuario no existe", + "status": "error" + } + ] \ No newline at end of file