From 46be88a3b2a3dcb122aadb1f64d1e367855b3ac7 Mon Sep 17 00:00:00 2001 From: Jemn Date: Fri, 2 Aug 2024 18:55:45 -0500 Subject: [PATCH 01/14] version inicial --- .idea/.gitignore | 3 + .idea/compiler.xml | 18 ++ .idea/encodings.xml | 6 + .idea/jarRepositories.xml | 20 ++ .idea/misc.xml | 12 + .idea/vcs.xml | 6 + HELP.md | 32 +++ mvnw | 259 ++++++++++++++++++ mvnw.cmd | 149 ++++++++++ pom.xml | 80 ++++++ .../challenge/ChallengeApplication.java | 13 + src/main/resources/application.yml | 14 + .../challenge/ChallengeApplicationTests.java | 13 + target/classes/application.yml | 14 + .../challenge/ChallengeApplication.class | Bin 0 -> 762 bytes .../challenge/ChallengeApplicationTests.class | Bin 0 -> 560 bytes 16 files changed, 639 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 HELP.md create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/com/interbank/challenge/ChallengeApplication.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/com/interbank/challenge/ChallengeApplicationTests.java create mode 100644 target/classes/application.yml create mode 100644 target/classes/com/interbank/challenge/ChallengeApplication.class create mode 100644 target/test-classes/com/interbank/challenge/ChallengeApplicationTests.class diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..cf01fda --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..63e9001 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5157874 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/HELP.md b/HELP.md new file mode 100644 index 0000000..e2f34be --- /dev/null +++ b/HELP.md @@ -0,0 +1,32 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/3.3.2/maven-plugin) +* [Create an OCI image](https://docs.spring.io/spring-boot/3.3.2/maven-plugin/build-image.html) +* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data) +* [Validation](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#io.validation) +* [Spring Data Reactive MongoDB](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#data.nosql.mongodb) +* [Spring Reactive Web](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.reactive) +* [Spring Security](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.security) + +### Guides +The following guides illustrate how to use some features concretely: + +* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) +* [Validation](https://spring.io/guides/gs/validating-form-input/) +* [Accessing Data with MongoDB](https://spring.io/guides/gs/accessing-data-mongodb/) +* [Building a Reactive RESTful Web Service](https://spring.io/guides/gs/reactive-rest-service/) +* [Securing a Web Application](https://spring.io/guides/gs/securing-web/) +* [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/) +* [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/) + +### Maven Parent overrides + +Due to Maven's design, elements are inherited from the parent POM to the project POM. +While most of the inheritance is fine, it also inherits unwanted elements like `` and `` from the parent. +To prevent this, the project POM contains empty overrides for these elements. +If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides. + diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# 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:]' +} + +# 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 <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_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 +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d2d011e --- /dev/null +++ b/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.2 + + + com.interbank + challenge + 0.0.1-SNAPSHOT + challenge + Demo project for Spring Boot + + 17 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-data-mongodb-reactive + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/interbank/challenge/ChallengeApplication.java b/src/main/java/com/interbank/challenge/ChallengeApplication.java new file mode 100644 index 0000000..469c359 --- /dev/null +++ b/src/main/java/com/interbank/challenge/ChallengeApplication.java @@ -0,0 +1,13 @@ +package com.interbank.challenge; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ChallengeApplication { + + public static void main(String[] args) { + SpringApplication.run(ChallengeApplication.class, args); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..3a2ba6a --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,14 @@ +spring: + application: + name: challenge + data: + mongodb: + uri: mongodb://localhost:27017/challenge_prod + security: + user: + name: admin + password: admin + autoconfigure: + exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +server: + port: 8082 \ No newline at end of file diff --git a/src/test/java/com/interbank/challenge/ChallengeApplicationTests.java b/src/test/java/com/interbank/challenge/ChallengeApplicationTests.java new file mode 100644 index 0000000..1352882 --- /dev/null +++ b/src/test/java/com/interbank/challenge/ChallengeApplicationTests.java @@ -0,0 +1,13 @@ +package com.interbank.challenge; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ChallengeApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..7cf49b5 --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,14 @@ +spring: + application: + name: challenge + data: + mongodb: + uri: mongodb://localhost:27017/estrasys_prod + security: + user: + name: admin + password: admin + autoconfigure: + exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +server: + port: 8082 \ No newline at end of file diff --git a/target/classes/com/interbank/challenge/ChallengeApplication.class b/target/classes/com/interbank/challenge/ChallengeApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..39912ce711ce88e18b8f73447dfcd68c0f914c31 GIT binary patch literal 762 zcmb7CO>fgc5Ph2_b(*B4q|g#t4&0Kc>fX2nDnTSph9ZK(p{KRGbvCtkqxHJwZ*czqrRWWOd>Vq#>^33jhyqSG7clS590N&wM2MuflX!g*;Cd1B3TnU~Eo$%9WC1c01 z`BrJ=P8c>u<3)fr!%=K9uC$Xj68e(IOOd8hCz8+po!+n4sfvYDMt9IbH$bn4Z9HL^ z7@P2XZIw5QQam@?ky#v4nY&M|+)dtksump7m-KlsKJQX_Ockxt7$Dn?W8r>y> z>fZjS0m}@IrO7yIr)(_rjLV5gQ>7EduNSv|o~I~t8^dcigIuaJWKM&U21c`JK{kzi{gSIp@+p~*|7?a4>eW@L0YJ>$+hrZUvg~=$l z9T}VOX-Nwb=141Xkt6p*lTveFN-Nb3`ayU8-0sLf8iUgyR^%3S@?b@#o{XLG*cith z>GAnkd=M(GC?49tE;Q&9{P9TN01Q^>#>p}tId}#364FgVp9Z3|2;1KUWLL=pSfgwu gkPpf8b-#$MA4GbqK Date: Sun, 4 Aug 2024 12:36:00 -0500 Subject: [PATCH 02/14] se agrega archivo gitignore, configuracion de spring security, creacion de controller login para manejo con jwt. --- .gitignore | 39 +++++++ .idea/compiler.xml | 1 + pom.xml | 10 ++ .../config/GlobalExceptionHandler.java | 25 +++++ .../challenge/config/SecurityConfig.java | 68 ++++++++++++ .../challenge/config/jwt/JwtFilter.java | 57 ++++++++++ .../challenge/config/jwt/JwtUtil.java | 52 +++++++++ .../challenge/controller/LoginController.java | 103 ++++++++++++++++++ .../challenge/entity/RoleDocument.java | 28 +++++ .../challenge/entity/UsuarioDocument.java | 49 +++++++++ .../entity/dto/request/SigninRequest.java | 14 +++ .../entity/dto/request/SignupRequest.java | 15 +++ .../entity/dto/response/MessageResponse.java | 12 ++ .../entity/dto/response/SigninResponse.java | 20 ++++ .../entity/dto/response/SignupResponse.java | 16 +++ .../challenge/reporitory/RoleRepository.java | 10 ++ .../reporitory/UsuarioRepository.java | 13 +++ .../service/UserDetailsServiceImpl.java | 28 +++++ .../challenge/service/UsuarioService.java | 11 ++ .../challenge/service/UsuarioServiceImpl.java | 41 +++++++ .../interbank/challenge/util/Constantes.java | 8 ++ src/main/resources/application.yml | 5 + target/classes/application.yml | 7 +- 23 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 src/main/java/com/interbank/challenge/config/GlobalExceptionHandler.java create mode 100644 src/main/java/com/interbank/challenge/config/SecurityConfig.java create mode 100644 src/main/java/com/interbank/challenge/config/jwt/JwtFilter.java create mode 100644 src/main/java/com/interbank/challenge/config/jwt/JwtUtil.java create mode 100644 src/main/java/com/interbank/challenge/controller/LoginController.java create mode 100644 src/main/java/com/interbank/challenge/entity/RoleDocument.java create mode 100644 src/main/java/com/interbank/challenge/entity/UsuarioDocument.java create mode 100644 src/main/java/com/interbank/challenge/entity/dto/request/SigninRequest.java create mode 100644 src/main/java/com/interbank/challenge/entity/dto/request/SignupRequest.java create mode 100644 src/main/java/com/interbank/challenge/entity/dto/response/MessageResponse.java create mode 100644 src/main/java/com/interbank/challenge/entity/dto/response/SigninResponse.java create mode 100644 src/main/java/com/interbank/challenge/entity/dto/response/SignupResponse.java create mode 100644 src/main/java/com/interbank/challenge/reporitory/RoleRepository.java create mode 100644 src/main/java/com/interbank/challenge/reporitory/UsuarioRepository.java create mode 100644 src/main/java/com/interbank/challenge/service/UserDetailsServiceImpl.java create mode 100644 src/main/java/com/interbank/challenge/service/UsuarioService.java create mode 100644 src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java create mode 100644 src/main/java/com/interbank/challenge/util/Constantes.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c43043a --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Directorios de Maven +target/ + +# Archivos de configuración locales +*.iml +.idea/ +*.classpath +*.project +*.settings/ +*.factorypath + +# Archivos de sistema +.DS_Store +Thumbs.db + +# Archivos de configuración de IntelliJ +*.ipr +*.iws +*.bak +*.swp + +# Archivos de log +*.log + +# Archivos de compilación +*.class + +# Archivos de compilación de Java +*.jar +*.war +*.ear + +# Configuración de sistema operativo +*.swp +.sass-cache/ +.vscode/ + +# Archivos específicos de ambiente +.env diff --git a/.idea/compiler.xml b/.idea/compiler.xml index cf01fda..4d33edb 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -2,6 +2,7 @@ + diff --git a/pom.xml b/pom.xml index d2d011e..84a5df9 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,16 @@ spring-security-test test + + io.jsonwebtoken + jjwt + 0.9.1 + + + javax.xml.bind + jaxb-api + 2.3.1 + diff --git a/src/main/java/com/interbank/challenge/config/GlobalExceptionHandler.java b/src/main/java/com/interbank/challenge/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..0b8db4b --- /dev/null +++ b/src/main/java/com/interbank/challenge/config/GlobalExceptionHandler.java @@ -0,0 +1,25 @@ +package com.interbank.challenge.config; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.bind.support.WebExchangeBindException; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(WebExchangeBindException.class) + public Mono>> handleValidationExceptions(WebExchangeBindException ex) { + Map errors = new HashMap<>(); + for (FieldError error : ex.getFieldErrors()) { + errors.put(error.getField(), error.getDefaultMessage()); + } + return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors)); + } +} diff --git a/src/main/java/com/interbank/challenge/config/SecurityConfig.java b/src/main/java/com/interbank/challenge/config/SecurityConfig.java new file mode 100644 index 0000000..7ecf066 --- /dev/null +++ b/src/main/java/com/interbank/challenge/config/SecurityConfig.java @@ -0,0 +1,68 @@ +package com.interbank.challenge.config; + +import com.interbank.challenge.config.jwt.JwtFilter; +import com.interbank.challenge.config.jwt.JwtUtil; +import com.interbank.challenge.service.UserDetailsServiceImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.security.authentication.ReactiveAuthenticationManager; +import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; +import org.springframework.security.config.web.server.SecurityWebFiltersOrder; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, JwtUtil jwtUtil, ReactiveUserDetailsService userDetailsService) { + JwtFilter jwtRequestFilter = new JwtFilter(jwtUtil, userDetailsService); + + http.csrf().disable() + .authorizeExchange() + .pathMatchers("/", + "/swagger-ui.html", + "/favicon.ico", + "swagger-ui/index.html", + "/swagger-ui/**", + "/v3/api-docs/**", + "/v3/api-docs.yaml", + "/api/login/signin", + "/api/login/signup", + "/api/login/listuser").permitAll() + .anyExchange().authenticated() + .and() + .addFilterAt(jwtRequestFilter, SecurityWebFiltersOrder.AUTHENTICATION) + .securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); + + return http.build(); + } + + @Bean + @Primary + public ReactiveUserDetailsService userDetailsService(UserDetailsServiceImpl userDetailsService) { + return userDetailsService; + } + + @Bean + public ReactiveAuthenticationManager authenticationManager(ReactiveUserDetailsService userDetailsService) { + UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = + new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService); + authenticationManager.setPasswordEncoder(passwordEncoder()); + return authenticationManager; + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public JwtUtil jwtUtil() { + return new JwtUtil(); + } +} \ No newline at end of file diff --git a/src/main/java/com/interbank/challenge/config/jwt/JwtFilter.java b/src/main/java/com/interbank/challenge/config/jwt/JwtFilter.java new file mode 100644 index 0000000..ee3c08d --- /dev/null +++ b/src/main/java/com/interbank/challenge/config/jwt/JwtFilter.java @@ -0,0 +1,57 @@ +package com.interbank.challenge.config.jwt; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +import java.util.List; + +@Component +public class JwtFilter implements WebFilter { + + private final JwtUtil jwtUtil; + private final ReactiveUserDetailsService userDetailsService; + + public JwtFilter(JwtUtil jwtUtil, ReactiveUserDetailsService userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + + final List authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION); + + if (authorizationHeader != null && !authorizationHeader.isEmpty() && authorizationHeader.get(0).startsWith("Bearer ")) { + String jwt = authorizationHeader.get(0).substring(7); + String username = jwtUtil.extractUsername(jwt); + + if (username != null) { + return userDetailsService.findByUsername(username) + .flatMap(userDetails -> { + if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + SecurityContext securityContext = new SecurityContextImpl(authentication); + + return chain.filter(exchange) + .contextWrite(Context.of(SecurityContext.class, securityContext)); + } else { + return chain.filter(exchange); + } + }); + } + } + return chain.filter(exchange); + } +} \ No newline at end of file diff --git a/src/main/java/com/interbank/challenge/config/jwt/JwtUtil.java b/src/main/java/com/interbank/challenge/config/jwt/JwtUtil.java new file mode 100644 index 0000000..bf97d0e --- /dev/null +++ b/src/main/java/com/interbank/challenge/config/jwt/JwtUtil.java @@ -0,0 +1,52 @@ +package com.interbank.challenge.config.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.function.Function; + +@Component +public class JwtUtil { + + @Value("${app.jwt.secret}") + private String jwtSecret; + + @Value("${app.jwt.expirationMs}") + private int jwtExpirationMs; + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody(); + } + + private Boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + public String generateToken(String username) { + return Jwts.builder().setSubject(username).setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) + .signWith(SignatureAlgorithm.HS256, jwtSecret).compact(); + } + + public Boolean validateToken(String token, String username) { + final String extractedUsername = extractUsername(token); + return (extractedUsername.equals(username) && !isTokenExpired(token)); + } +} \ No newline at end of file diff --git a/src/main/java/com/interbank/challenge/controller/LoginController.java b/src/main/java/com/interbank/challenge/controller/LoginController.java new file mode 100644 index 0000000..17f6de4 --- /dev/null +++ b/src/main/java/com/interbank/challenge/controller/LoginController.java @@ -0,0 +1,103 @@ +package com.interbank.challenge.controller; + +import com.interbank.challenge.config.jwt.JwtUtil; +import com.interbank.challenge.entity.UsuarioDocument; +import com.interbank.challenge.entity.dto.request.SigninRequest; +import com.interbank.challenge.entity.dto.response.MessageResponse; +import com.interbank.challenge.entity.dto.response.SigninResponse; +import com.interbank.challenge.service.UsuarioService; +import com.interbank.challenge.util.Constantes; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/login") +public class LoginController { + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private ReactiveUserDetailsService CustomUserDetailsService; + + @Autowired + private UsuarioService usuarioService; + + @PostMapping("/signin") + public Mono> createAuthenticationToken(@Valid @RequestBody SigninRequest signinRequest) { + return usuarioService.findByUsername(signinRequest.getUsername()) + .flatMap(userDetails -> { + if (userDetails != null && usuarioService.comparePasswords(signinRequest.getPassword(), userDetails.getPassword())) { + String jwt = jwtUtil.generateToken(signinRequest.getUsername()); + return Mono.just(ResponseEntity.ok(SigninResponse.builder() + .accessToken(jwt) + .tokenType(Constantes.BEARER) + .id(userDetails.getId()) + .username(signinRequest.getUsername()) + .email(userDetails.getEmail()) + .locked(userDetails.getLocked()) + .disable(userDetails.getDisable()) + .role(userDetails.getRoleDocument().getRole()) + .build())); + } else { + return Mono.just(ResponseEntity.status(401).body(MessageResponse.builder() + .codeResponse(401) + .messageResponse(Constantes.INVALID_CREDENTIAL) + .build())); + } + }); + } + + @PostMapping("/signup") + public Mono> saveUser(@RequestBody UsuarioDocument user) { + + return usuarioService.findByUsername(user.getUsername()) + .flatMap(existingUser -> { + return Mono.just(ResponseEntity.status(HttpStatus.CONFLICT).body(MessageResponse.builder() + .codeResponse(HttpStatus.CONFLICT.value()) + .messageResponse("User already exists") + .build())); + }) + .switchIfEmpty( + usuarioService.saveUser(user) + .map(savedUser -> ResponseEntity.status(HttpStatus.CREATED).body(MessageResponse.builder() + .codeResponse(HttpStatus.CREATED.value()) + .messageResponse("Successful registration") + .build())) + .onErrorResume(e -> { + System.err.println("Error saving user: " + e.getMessage()); + e.printStackTrace(); + return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(MessageResponse.builder() + .codeResponse(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .messageResponse("Service error") + .build())); + }) + ); + } + + @PostMapping("/listuser") + public Mono> listuser(@RequestHeader("Authorization") String token, @Valid @RequestBody SigninRequest signinReques) { + + if (token.startsWith("Bearer ")) { + token = token.substring(7); + } + + if (jwtUtil.validateToken(token, signinReques.getUsername())) { + return Mono.just(ResponseEntity.status(HttpStatus.OK).body(MessageResponse.builder() + .codeResponse(HttpStatus.OK.value()) + .messageResponse("Successful registration") + .build())); + } else { + return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(MessageResponse.builder() + .codeResponse(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .messageResponse("Service error") + .build())); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/interbank/challenge/entity/RoleDocument.java b/src/main/java/com/interbank/challenge/entity/RoleDocument.java new file mode 100644 index 0000000..06a025f --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/RoleDocument.java @@ -0,0 +1,28 @@ +package com.interbank.challenge.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +@Document(collection = "table_role") +@Data +@NoArgsConstructor +public class RoleDocument { + + public RoleDocument(String role, LocalDate date){ + this.role=role; + this.date=date; + } + @Id + private String id; + + private String role; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate date; + +} diff --git a/src/main/java/com/interbank/challenge/entity/UsuarioDocument.java b/src/main/java/com/interbank/challenge/entity/UsuarioDocument.java new file mode 100644 index 0000000..29e9523 --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/UsuarioDocument.java @@ -0,0 +1,49 @@ +package com.interbank.challenge.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "table_user") +@Data +@NoArgsConstructor +public class UsuarioDocument { + + public UsuarioDocument(String username, String password, String email, Boolean locked, Boolean disable) { + this.username = username; + this.password = password; + this.email = email; + this.locked = locked; + this.disable = disable; + } + + public UsuarioDocument(String username, String password, String email, Boolean locked, Boolean disable, RoleDocument roleDocument) { + this.username = username; + this.password = password; + this.email = email; + this.locked = locked; + this.disable = disable; + this.roleDocument = roleDocument; + } + + @Id + private String id; + + private String username; + + private String password; + + private String email; + + private Boolean locked; + + private Boolean disable; + + private RoleDocument roleDocument; + + +} + + + diff --git a/src/main/java/com/interbank/challenge/entity/dto/request/SigninRequest.java b/src/main/java/com/interbank/challenge/entity/dto/request/SigninRequest.java new file mode 100644 index 0000000..f3175d4 --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/request/SigninRequest.java @@ -0,0 +1,14 @@ +package com.interbank.challenge.entity.dto.request; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Data +public class SigninRequest { + + @NotEmpty(message = "Username cannot be empty") + private String username; + + @NotEmpty(message = "Password cannot be empty") + private String password; +} diff --git a/src/main/java/com/interbank/challenge/entity/dto/request/SignupRequest.java b/src/main/java/com/interbank/challenge/entity/dto/request/SignupRequest.java new file mode 100644 index 0000000..d977bff --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/request/SignupRequest.java @@ -0,0 +1,15 @@ +package com.interbank.challenge.entity.dto.request; + +import lombok.Data; + +@Data +public class SignupRequest { + + private String user; + private String password; + private String email; + private boolean lock; + private boolean disable; + private String role; + +} diff --git a/src/main/java/com/interbank/challenge/entity/dto/response/MessageResponse.java b/src/main/java/com/interbank/challenge/entity/dto/response/MessageResponse.java new file mode 100644 index 0000000..3b0ea7f --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/response/MessageResponse.java @@ -0,0 +1,12 @@ +package com.interbank.challenge.entity.dto.response; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class MessageResponse { + + private Integer codeResponse; + private String messageResponse; +} diff --git a/src/main/java/com/interbank/challenge/entity/dto/response/SigninResponse.java b/src/main/java/com/interbank/challenge/entity/dto/response/SigninResponse.java new file mode 100644 index 0000000..2851ade --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/response/SigninResponse.java @@ -0,0 +1,20 @@ +package com.interbank.challenge.entity.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@AllArgsConstructor +@Builder +public class SigninResponse { + + private String accessToken; + private String tokenType = "Bearer"; + private String id; + private String username; + private String email; + private Boolean locked = true; + private Boolean disable = true; + private String role; +} diff --git a/src/main/java/com/interbank/challenge/entity/dto/response/SignupResponse.java b/src/main/java/com/interbank/challenge/entity/dto/response/SignupResponse.java new file mode 100644 index 0000000..80ba6c4 --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/response/SignupResponse.java @@ -0,0 +1,16 @@ +package com.interbank.challenge.entity.dto.response; + +import lombok.Data; + +@Data +public class SignupResponse { + + private String accessToken; + private String tokenType = "Bearer"; + private String id; + private String username; + private String email; + private Boolean locked = true; + private Boolean disable = true; + private String role; +} diff --git a/src/main/java/com/interbank/challenge/reporitory/RoleRepository.java b/src/main/java/com/interbank/challenge/reporitory/RoleRepository.java new file mode 100644 index 0000000..e903ea5 --- /dev/null +++ b/src/main/java/com/interbank/challenge/reporitory/RoleRepository.java @@ -0,0 +1,10 @@ +package com.interbank.challenge.reporitory; + +import com.interbank.challenge.entity.RoleDocument; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RoleRepository extends ReactiveMongoRepository { + +} diff --git a/src/main/java/com/interbank/challenge/reporitory/UsuarioRepository.java b/src/main/java/com/interbank/challenge/reporitory/UsuarioRepository.java new file mode 100644 index 0000000..6591990 --- /dev/null +++ b/src/main/java/com/interbank/challenge/reporitory/UsuarioRepository.java @@ -0,0 +1,13 @@ +package com.interbank.challenge.reporitory; + +import com.interbank.challenge.entity.UsuarioDocument; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public interface UsuarioRepository extends ReactiveMongoRepository { + + Mono findByUsername(String username); + +} diff --git a/src/main/java/com/interbank/challenge/service/UserDetailsServiceImpl.java b/src/main/java/com/interbank/challenge/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..929fcc6 --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/UserDetailsServiceImpl.java @@ -0,0 +1,28 @@ +package com.interbank.challenge.service; +; +import com.interbank.challenge.reporitory.UsuarioRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +public class UserDetailsServiceImpl implements ReactiveUserDetailsService { + + @Autowired + private UsuarioRepository usuarioRepository; + + @Override + public Mono findByUsername(String username) { + return usuarioRepository.findByUsername(username) + .switchIfEmpty(Mono.error(new UsernameNotFoundException("User not found"))) + .map(user -> org.springframework.security.core.userdetails.User + .withUsername(user.getUsername()) + .password(user.getPassword()) + //.roles(user.getRoleDocument().getRole()) validar usar roles o authorites + .authorities(user.getRoleDocument().getRole()) + .build()); + } +} diff --git a/src/main/java/com/interbank/challenge/service/UsuarioService.java b/src/main/java/com/interbank/challenge/service/UsuarioService.java new file mode 100644 index 0000000..00704ce --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/UsuarioService.java @@ -0,0 +1,11 @@ +package com.interbank.challenge.service; +; +import com.interbank.challenge.entity.UsuarioDocument; +import reactor.core.publisher.Mono; + +public interface UsuarioService { + + Mono findByUsername(String username); + Mono saveUser(UsuarioDocument user); + boolean comparePasswords(String rawPassword, String encodedPassword); +} diff --git a/src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java b/src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java new file mode 100644 index 0000000..34e03b7 --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java @@ -0,0 +1,41 @@ +package com.interbank.challenge.service; +; +import com.interbank.challenge.entity.UsuarioDocument; +import com.interbank.challenge.reporitory.RoleRepository; +import com.interbank.challenge.reporitory.UsuarioRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.time.LocalDate; + +@Service +public class UsuarioServiceImpl implements UsuarioService{ + + @Autowired + private UsuarioRepository usuarioRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private BCryptPasswordEncoder bCryptPasswordEncoder; + + @Override + public Mono findByUsername(String username) { + return usuarioRepository.findByUsername(username); + } + + @Override + public Mono saveUser(UsuarioDocument user) { + user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); + user.getRoleDocument().setDate(LocalDate.now()); + return usuarioRepository.save(user); + } + + @Override + public boolean comparePasswords(String rawPassword, String encodedPassword) { + return bCryptPasswordEncoder.matches(rawPassword, encodedPassword); + } +} diff --git a/src/main/java/com/interbank/challenge/util/Constantes.java b/src/main/java/com/interbank/challenge/util/Constantes.java new file mode 100644 index 0000000..efaa9e7 --- /dev/null +++ b/src/main/java/com/interbank/challenge/util/Constantes.java @@ -0,0 +1,8 @@ +package com.interbank.challenge.util; + +public class Constantes { + + public static final String HTTP_401 = "401"; + public static final String INVALID_CREDENTIAL = "Invalid credentials"; + public static final String BEARER = "Bearer"; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3a2ba6a..ed39413 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,8 @@ +app: + jwt: + secret: $2y$10$dJMr85s.761OiPWe.m2nRem3GCWiTWQmuPGY4MpayC7z6DH6LC4v2 + expirationMs: 300000 #5min + spring: application: name: challenge diff --git a/target/classes/application.yml b/target/classes/application.yml index 7cf49b5..ed39413 100644 --- a/target/classes/application.yml +++ b/target/classes/application.yml @@ -1,9 +1,14 @@ +app: + jwt: + secret: $2y$10$dJMr85s.761OiPWe.m2nRem3GCWiTWQmuPGY4MpayC7z6DH6LC4v2 + expirationMs: 300000 #5min + spring: application: name: challenge data: mongodb: - uri: mongodb://localhost:27017/estrasys_prod + uri: mongodb://localhost:27017/challenge_prod security: user: name: admin From a69b7f4a896f7cf2bcc9f46e4c63c38f5cc10029 Mon Sep 17 00:00:00 2001 From: Jemn Date: Sun, 4 Aug 2024 12:47:29 -0500 Subject: [PATCH 03/14] agregando openapi para meanejo de documentacion --- pom.xml | 5 + .../challenge/controller/LoginController.java | 4 + src/main/resources/application.yml | 17 ++- .../resources/documentacion/api-docs.yaml | 121 ++++++++++++++++++ target/classes/application.yml | 17 ++- 5 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/documentacion/api-docs.yaml diff --git a/pom.xml b/pom.xml index 84a5df9..9f2554c 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,11 @@ jaxb-api 2.3.1 + + org.springdoc + springdoc-openapi-starter-webflux-api + 2.6.0 + diff --git a/src/main/java/com/interbank/challenge/controller/LoginController.java b/src/main/java/com/interbank/challenge/controller/LoginController.java index 17f6de4..e220fa2 100644 --- a/src/main/java/com/interbank/challenge/controller/LoginController.java +++ b/src/main/java/com/interbank/challenge/controller/LoginController.java @@ -7,6 +7,7 @@ import com.interbank.challenge.entity.dto.response.SigninResponse; import com.interbank.challenge.service.UsuarioService; import com.interbank.challenge.util.Constantes; +import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -29,6 +30,7 @@ public class LoginController { private UsuarioService usuarioService; @PostMapping("/signin") + @Operation(summary = "Metodo signin", description = "Devuelve el token en jwt") public Mono> createAuthenticationToken(@Valid @RequestBody SigninRequest signinRequest) { return usuarioService.findByUsername(signinRequest.getUsername()) .flatMap(userDetails -> { @@ -54,6 +56,7 @@ public Mono> createAuthenticationToken(@Valid @RequestBody Sig } @PostMapping("/signup") + @Operation(summary = "Metodo signup", description = "Registra un Usuario en la base de datos") public Mono> saveUser(@RequestBody UsuarioDocument user) { return usuarioService.findByUsername(user.getUsername()) @@ -81,6 +84,7 @@ public Mono> saveUser(@RequestBody UsuarioDocume } @PostMapping("/listuser") + @Operation(summary = "Metodo listuser", description = "Lista de Usuario en la base de datos") public Mono> listuser(@RequestHeader("Authorization") String token, @Valid @RequestBody SigninRequest signinReques) { if (token.startsWith("Bearer ")) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ed39413..ce2749b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,4 +16,19 @@ spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration server: - port: 8082 \ No newline at end of file + port: 8082 + +logging: + level: + root: info + org: + springframework: + web: info + security: DEBUG + hibernate: error + +springdoc: + api-docs: + path: /v3/api-docs + swagger-ui: + path: /swagger-ui.html \ No newline at end of file diff --git a/src/main/resources/documentacion/api-docs.yaml b/src/main/resources/documentacion/api-docs.yaml new file mode 100644 index 0000000..075b9cc --- /dev/null +++ b/src/main/resources/documentacion/api-docs.yaml @@ -0,0 +1,121 @@ +openapi: 3.0.1 +info: + title: OpenAPI definition + version: v0 +servers: +- url: http://localhost:8082 + description: Generated server url +paths: + /api/login/signup: + post: + tags: + - login-controller + summary: Metodo signup + description: Registra un Usuario en la base de datos + operationId: saveUser + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UsuarioDocument" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + $ref: "#/components/schemas/MessageResponse" + /api/login/signin: + post: + tags: + - login-controller + summary: Metodo signin + description: Devuelve el token en jwt + operationId: createAuthenticationToken + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SigninRequest" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + type: object + /api/login/listuser: + post: + tags: + - login-controller + summary: Metodo listuser + description: Lista de Usuario en la base de datos + operationId: listuser + parameters: + - name: Authorization + in: header + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SigninRequest" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + type: object +components: + schemas: + RoleDocument: + type: object + properties: + id: + type: string + role: + type: string + date: + type: string + format: date + UsuarioDocument: + type: object + properties: + id: + type: string + username: + type: string + password: + type: string + email: + type: string + locked: + type: boolean + disable: + type: boolean + roleDocument: + $ref: "#/components/schemas/RoleDocument" + MessageResponse: + type: object + properties: + codeResponse: + type: integer + format: int32 + messageResponse: + type: string + SigninRequest: + required: + - password + - username + type: object + properties: + username: + type: string + password: + type: string diff --git a/target/classes/application.yml b/target/classes/application.yml index ed39413..ce2749b 100644 --- a/target/classes/application.yml +++ b/target/classes/application.yml @@ -16,4 +16,19 @@ spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration server: - port: 8082 \ No newline at end of file + port: 8082 + +logging: + level: + root: info + org: + springframework: + web: info + security: DEBUG + hibernate: error + +springdoc: + api-docs: + path: /v3/api-docs + swagger-ui: + path: /swagger-ui.html \ No newline at end of file From 83df5b8edf8d9ae676c1d0acc394a088e6b228f6 Mon Sep 17 00:00:00 2001 From: Jemn Date: Tue, 6 Aug 2024 12:10:52 -0500 Subject: [PATCH 04/14] configuracion de kafka producer y consumer y creacion de controller Transaccion --- pom.xml | 8 +-- .../challenge/config/SecurityConfig.java | 4 +- .../challenge/config/kafka/KafkaConsumer.java | 36 +++++++++++++ .../challenge/config/kafka/KafkaProducer.java | 28 ++++++++++ .../challenge/controller/LoginController.java | 1 + .../controller/TransactionController.java | 32 +++++++++++ .../challenge/entity/TransactionDocument.java | 42 +++++++++++++++ .../reporitory/TransactionRepository.java | 11 ++++ .../service/AntifraudeConsumerService.java | 6 +++ .../AntifraudeConsumerServiceImpl.java | 14 +++++ .../challenge/service/TransactionService.java | 12 +++++ .../service/TransactionServiceImpl.java | 53 +++++++++++++++++++ src/main/resources/application.yml | 14 ++++- target/classes/application.yml | 14 ++++- 14 files changed, 266 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java create mode 100644 src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java create mode 100644 src/main/java/com/interbank/challenge/controller/TransactionController.java create mode 100644 src/main/java/com/interbank/challenge/entity/TransactionDocument.java create mode 100644 src/main/java/com/interbank/challenge/reporitory/TransactionRepository.java create mode 100644 src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java create mode 100644 src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java create mode 100644 src/main/java/com/interbank/challenge/service/TransactionService.java create mode 100644 src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java diff --git a/pom.xml b/pom.xml index 9f2554c..727c8a4 100644 --- a/pom.xml +++ b/pom.xml @@ -17,10 +17,6 @@ 17 - - org.springframework.boot - spring-boot-starter-data-jpa - org.springframework.boot spring-boot-starter-data-mongodb-reactive @@ -37,6 +33,10 @@ org.springframework.boot spring-boot-starter-webflux + + org.springframework.kafka + spring-kafka + org.projectlombok diff --git a/src/main/java/com/interbank/challenge/config/SecurityConfig.java b/src/main/java/com/interbank/challenge/config/SecurityConfig.java index 7ecf066..cd7c2cc 100644 --- a/src/main/java/com/interbank/challenge/config/SecurityConfig.java +++ b/src/main/java/com/interbank/challenge/config/SecurityConfig.java @@ -33,7 +33,9 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, Jw "/v3/api-docs.yaml", "/api/login/signin", "/api/login/signup", - "/api/login/listuser").permitAll() + "/api/login/listuser", + "/api/transactions/save", + "/api/transactions/list/*").permitAll() .anyExchange().authenticated() .and() .addFilterAt(jwtRequestFilter, SecurityWebFiltersOrder.AUTHENTICATION) diff --git a/src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java b/src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java new file mode 100644 index 0000000..1bb05b3 --- /dev/null +++ b/src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java @@ -0,0 +1,36 @@ +package com.interbank.challenge.config.kafka; + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; + +import java.util.HashMap; +import java.util.Map; + +@EnableKafka +@Configuration +public class KafkaConsumer { + + @Bean + public ConsumerFactory consumerFactory() { + Map configProps = new HashMap<>(); + configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + configProps.put(ConsumerConfig.GROUP_ID_CONFIG, "Interbank"); + configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + return new DefaultKafkaConsumerFactory<>(configProps); + } + + @Bean + public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(consumerFactory()); + return factory; + } + +} diff --git a/src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java b/src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java new file mode 100644 index 0000000..21cc759 --- /dev/null +++ b/src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java @@ -0,0 +1,28 @@ +package com.interbank.challenge.config.kafka; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; + +import java.util.HashMap; +import java.util.Map; + +public class KafkaProducer { + + @Bean + public ProducerFactory producerFactory() { + Map configProps = new HashMap<>(); + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return new DefaultKafkaProducerFactory<>(configProps); + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } +} diff --git a/src/main/java/com/interbank/challenge/controller/LoginController.java b/src/main/java/com/interbank/challenge/controller/LoginController.java index e220fa2..047763d 100644 --- a/src/main/java/com/interbank/challenge/controller/LoginController.java +++ b/src/main/java/com/interbank/challenge/controller/LoginController.java @@ -32,6 +32,7 @@ public class LoginController { @PostMapping("/signin") @Operation(summary = "Metodo signin", description = "Devuelve el token en jwt") public Mono> createAuthenticationToken(@Valid @RequestBody SigninRequest signinRequest) { + return usuarioService.findByUsername(signinRequest.getUsername()) .flatMap(userDetails -> { if (userDetails != null && usuarioService.comparePasswords(signinRequest.getPassword(), userDetails.getPassword())) { diff --git a/src/main/java/com/interbank/challenge/controller/TransactionController.java b/src/main/java/com/interbank/challenge/controller/TransactionController.java new file mode 100644 index 0000000..4d5f98a --- /dev/null +++ b/src/main/java/com/interbank/challenge/controller/TransactionController.java @@ -0,0 +1,32 @@ +package com.interbank.challenge.controller; + +import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.service.TransactionService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/transactions") +public class TransactionController { + + @Autowired + TransactionService transactionService; + + @PostMapping("/save") + @Operation(summary = "Metodo createTransaction", description = "Crea una operación") + public Mono createTransaction(@RequestHeader("Authorization") String token, @Valid @RequestBody TransactionDocument transaction) { + System.out.println("****************************** ingreso createTransaction ***************************"); + return transactionService.createTransaction(transaction); + + } + + @GetMapping("/list/{id}") + @Operation(summary = "Metodo getTransaction", description = "Devuelve una operacion") + public Mono getTransaction(@RequestHeader("Authorization") String token, @Valid @PathVariable String id) { + System.out.println("****************************** ingreso id ***************************"); + return transactionService.getTransactionById(id); + } +} diff --git a/src/main/java/com/interbank/challenge/entity/TransactionDocument.java b/src/main/java/com/interbank/challenge/entity/TransactionDocument.java new file mode 100644 index 0000000..c53ee68 --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/TransactionDocument.java @@ -0,0 +1,42 @@ +package com.interbank.challenge.entity; + + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +@Document(collection = "table_transaction") +@Data +@NoArgsConstructor +public class TransactionDocument { + + public TransactionDocument(String accountExternalIdDebit, String accountExternalIdCredit, Integer tranferTypeId, Integer value, String transactionStatus, LocalDate createdAt) { + this.accountExternalIdDebit = accountExternalIdDebit; + this.accountExternalIdCredit = accountExternalIdCredit; + this.tranferTypeId = tranferTypeId; + this.value = value; + this.transactionStatus = transactionStatus; + this.createdAt = createdAt; + } + + @Id + private String id; + + private String accountExternalIdDebit; + + private String accountExternalIdCredit; + + private Integer tranferTypeId; + + private Integer value; + + private String transactionStatus; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate createdAt; + +} diff --git a/src/main/java/com/interbank/challenge/reporitory/TransactionRepository.java b/src/main/java/com/interbank/challenge/reporitory/TransactionRepository.java new file mode 100644 index 0000000..3dc7f1a --- /dev/null +++ b/src/main/java/com/interbank/challenge/reporitory/TransactionRepository.java @@ -0,0 +1,11 @@ +package com.interbank.challenge.reporitory; + +import com.interbank.challenge.entity.TransactionDocument; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TransactionRepository extends ReactiveMongoRepository { + +} + diff --git a/src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java b/src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java new file mode 100644 index 0000000..8872e1e --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java @@ -0,0 +1,6 @@ +package com.interbank.challenge.service; + +public interface AntifraudeConsumerService { + + public void listen(String message); +} diff --git a/src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java b/src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java new file mode 100644 index 0000000..45168d7 --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java @@ -0,0 +1,14 @@ +package com.interbank.challenge.service; + +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Service; + +@Service +public class AntifraudeConsumerServiceImpl implements AntifraudeConsumerService { + + @Override + @KafkaListener(topics = "registro-events", groupId = "Interbank") + public void listen(String message) { + System.out.println("Mensaje recibido: " + message); + } +} diff --git a/src/main/java/com/interbank/challenge/service/TransactionService.java b/src/main/java/com/interbank/challenge/service/TransactionService.java new file mode 100644 index 0000000..e27c79c --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/TransactionService.java @@ -0,0 +1,12 @@ +package com.interbank.challenge.service; + +import com.interbank.challenge.entity.TransactionDocument; +import reactor.core.publisher.Mono; + +public interface TransactionService { + + Mono createTransaction(TransactionDocument transaction) ; + Mono updateTransactionStatus(String transactionId, String status); + Mono getTransactionById(String transactionId); + +} diff --git a/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java new file mode 100644 index 0000000..49c4d5b --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java @@ -0,0 +1,53 @@ +package com.interbank.challenge.service; + +import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.reporitory.TransactionRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import java.time.LocalDate; + +@Service +public class TransactionServiceImpl implements TransactionService{ + + @Autowired + TransactionRepository transactionRepository; + + private final String TOPIC = "registro-events"; + + @Autowired + private KafkaTemplate kafkaTemplate; + + + public Mono createTransaction(TransactionDocument transaction) { + transaction.setTransactionStatus("pendiente"); + transaction.setCreatedAt(LocalDate.now()); + + System.out.println("preparando para enviar mensaje kafka"); + kafkaTemplate.send(TOPIC, "registro de operacion"); + System.out.println("fin mensaje kafka"); + + return transactionRepository.save(transaction); + + + } + + public Mono updateTransactionStatus(String transactionId, String status) { + return transactionRepository.findById(transactionId) + .switchIfEmpty(Mono.error(new RuntimeException("Transaction not found"))) + .flatMap(transaction -> { + transaction.setTransactionStatus(status); + return transactionRepository.save(transaction) + .then(Mono.fromRunnable(() -> { + kafkaTemplate.send(TOPIC, "Transaction status updated: " + transactionId + " to " + status); + })); + }) + .then(); + } + + public Mono getTransactionById(String transactionId) { + return transactionRepository.findById(transactionId) + .switchIfEmpty(Mono.error(new RuntimeException("Lista Vacia"))); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ce2749b..bdb21a5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,8 +13,18 @@ spring: user: name: admin password: admin - autoconfigure: - exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + #autoconfigure: + #exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: myGroup + auto-offset-reset: earliest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer server: port: 8082 diff --git a/target/classes/application.yml b/target/classes/application.yml index ce2749b..bdb21a5 100644 --- a/target/classes/application.yml +++ b/target/classes/application.yml @@ -13,8 +13,18 @@ spring: user: name: admin password: admin - autoconfigure: - exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + #autoconfigure: + #exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: myGroup + auto-offset-reset: earliest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer server: port: 8082 From ac3986f76d52d04d08bdd1f05c8b7c8f16be89d6 Mon Sep 17 00:00:00 2001 From: Jemn Date: Tue, 6 Aug 2024 21:34:47 -0500 Subject: [PATCH 05/14] se agrega clase enum, y ajuste en Constantes --- pom.xml | 1 + .../challenge/config/kafka/KafkaConsumer.java | 5 +- .../challenge/config/kafka/KafkaProducer.java | 3 +- .../challenge/controller/LoginController.java | 7 ++- .../controller/TransactionController.java | 2 - .../service/AntifraudeConsumerService.java | 6 --- .../AntifraudeConsumerServiceImpl.java | 14 ------ .../challenge/service/AntifraudeService.java | 8 ++++ .../service/AntifraudeServiceImpl.java | 46 +++++++++++++++++++ .../challenge/service/TransactionService.java | 3 +- .../service/TransactionServiceImpl.java | 44 ++++++++++-------- .../challenge/service/UsuarioServiceImpl.java | 1 - .../interbank/challenge/util/Constante.java | 11 +++++ .../interbank/challenge/util/Constantes.java | 8 ---- .../challenge/util/TransactionStatus.java | 32 +++++++++++++ src/main/resources/application.yml | 1 + target/classes/application.yml | 1 + 17 files changed, 134 insertions(+), 59 deletions(-) delete mode 100644 src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java delete mode 100644 src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java create mode 100644 src/main/java/com/interbank/challenge/service/AntifraudeService.java create mode 100644 src/main/java/com/interbank/challenge/service/AntifraudeServiceImpl.java create mode 100644 src/main/java/com/interbank/challenge/util/Constante.java delete mode 100644 src/main/java/com/interbank/challenge/util/Constantes.java create mode 100644 src/main/java/com/interbank/challenge/util/TransactionStatus.java diff --git a/pom.xml b/pom.xml index 727c8a4..db68183 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,7 @@ springdoc-openapi-starter-webflux-api 2.6.0 + diff --git a/src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java b/src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java index 1bb05b3..2c372da 100644 --- a/src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java +++ b/src/main/java/com/interbank/challenge/config/kafka/KafkaConsumer.java @@ -1,5 +1,6 @@ package com.interbank.challenge.config.kafka; +import com.interbank.challenge.util.Constante; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.common.serialization.StringDeserializer; import org.springframework.context.annotation.Bean; @@ -19,8 +20,8 @@ public class KafkaConsumer { @Bean public ConsumerFactory consumerFactory() { Map configProps = new HashMap<>(); - configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); - configProps.put(ConsumerConfig.GROUP_ID_CONFIG, "Interbank"); + configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, Constante.SERVER_KAFKA ); + configProps.put(ConsumerConfig.GROUP_ID_CONFIG, Constante.MY_GROUP); configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); return new DefaultKafkaConsumerFactory<>(configProps); diff --git a/src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java b/src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java index 21cc759..258ed05 100644 --- a/src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java +++ b/src/main/java/com/interbank/challenge/config/kafka/KafkaProducer.java @@ -1,5 +1,6 @@ package com.interbank.challenge.config.kafka; +import com.interbank.challenge.util.Constante; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.context.annotation.Bean; @@ -15,7 +16,7 @@ public class KafkaProducer { @Bean public ProducerFactory producerFactory() { Map configProps = new HashMap<>(); - configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, Constante.SERVER_KAFKA); configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); return new DefaultKafkaProducerFactory<>(configProps); diff --git a/src/main/java/com/interbank/challenge/controller/LoginController.java b/src/main/java/com/interbank/challenge/controller/LoginController.java index 047763d..02bcaa5 100644 --- a/src/main/java/com/interbank/challenge/controller/LoginController.java +++ b/src/main/java/com/interbank/challenge/controller/LoginController.java @@ -6,7 +6,7 @@ import com.interbank.challenge.entity.dto.response.MessageResponse; import com.interbank.challenge.entity.dto.response.SigninResponse; import com.interbank.challenge.service.UsuarioService; -import com.interbank.challenge.util.Constantes; +import com.interbank.challenge.util.Constante; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -39,7 +39,7 @@ public Mono> createAuthenticationToken(@Valid @RequestBody Sig String jwt = jwtUtil.generateToken(signinRequest.getUsername()); return Mono.just(ResponseEntity.ok(SigninResponse.builder() .accessToken(jwt) - .tokenType(Constantes.BEARER) + .tokenType(Constante.BEARER) .id(userDetails.getId()) .username(signinRequest.getUsername()) .email(userDetails.getEmail()) @@ -50,7 +50,7 @@ public Mono> createAuthenticationToken(@Valid @RequestBody Sig } else { return Mono.just(ResponseEntity.status(401).body(MessageResponse.builder() .codeResponse(401) - .messageResponse(Constantes.INVALID_CREDENTIAL) + .messageResponse(Constante.INVALID_CREDENTIAL) .build())); } }); @@ -75,7 +75,6 @@ public Mono> saveUser(@RequestBody UsuarioDocume .build())) .onErrorResume(e -> { System.err.println("Error saving user: " + e.getMessage()); - e.printStackTrace(); return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(MessageResponse.builder() .codeResponse(HttpStatus.INTERNAL_SERVER_ERROR.value()) .messageResponse("Service error") diff --git a/src/main/java/com/interbank/challenge/controller/TransactionController.java b/src/main/java/com/interbank/challenge/controller/TransactionController.java index 4d5f98a..1568fee 100644 --- a/src/main/java/com/interbank/challenge/controller/TransactionController.java +++ b/src/main/java/com/interbank/challenge/controller/TransactionController.java @@ -18,7 +18,6 @@ public class TransactionController { @PostMapping("/save") @Operation(summary = "Metodo createTransaction", description = "Crea una operación") public Mono createTransaction(@RequestHeader("Authorization") String token, @Valid @RequestBody TransactionDocument transaction) { - System.out.println("****************************** ingreso createTransaction ***************************"); return transactionService.createTransaction(transaction); } @@ -26,7 +25,6 @@ public Mono createTransaction(@RequestHeader("Authorization @GetMapping("/list/{id}") @Operation(summary = "Metodo getTransaction", description = "Devuelve una operacion") public Mono getTransaction(@RequestHeader("Authorization") String token, @Valid @PathVariable String id) { - System.out.println("****************************** ingreso id ***************************"); return transactionService.getTransactionById(id); } } diff --git a/src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java b/src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java deleted file mode 100644 index 8872e1e..0000000 --- a/src/main/java/com/interbank/challenge/service/AntifraudeConsumerService.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.interbank.challenge.service; - -public interface AntifraudeConsumerService { - - public void listen(String message); -} diff --git a/src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java b/src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java deleted file mode 100644 index 45168d7..0000000 --- a/src/main/java/com/interbank/challenge/service/AntifraudeConsumerServiceImpl.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.interbank.challenge.service; - -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.stereotype.Service; - -@Service -public class AntifraudeConsumerServiceImpl implements AntifraudeConsumerService { - - @Override - @KafkaListener(topics = "registro-events", groupId = "Interbank") - public void listen(String message) { - System.out.println("Mensaje recibido: " + message); - } -} diff --git a/src/main/java/com/interbank/challenge/service/AntifraudeService.java b/src/main/java/com/interbank/challenge/service/AntifraudeService.java new file mode 100644 index 0000000..684bf05 --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/AntifraudeService.java @@ -0,0 +1,8 @@ +package com.interbank.challenge.service; + +import org.apache.kafka.clients.consumer.ConsumerRecord; + +public interface AntifraudeService { + + void listen(ConsumerRecord record); +} diff --git a/src/main/java/com/interbank/challenge/service/AntifraudeServiceImpl.java b/src/main/java/com/interbank/challenge/service/AntifraudeServiceImpl.java new file mode 100644 index 0000000..a66dda0 --- /dev/null +++ b/src/main/java/com/interbank/challenge/service/AntifraudeServiceImpl.java @@ -0,0 +1,46 @@ +package com.interbank.challenge.service; + +import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.reporitory.TransactionRepository; +import com.interbank.challenge.util.Constante; +import com.interbank.challenge.util.TransactionStatus; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +public class AntifraudeServiceImpl implements AntifraudeService { + + @Autowired + private KafkaTemplate kafkaTemplate; + + @Autowired + private TransactionRepository transactionRepository; + + private static final String TRANSACTION_CREATED_TOPIC = "transaction.created"; + private static final String TRANSACTION_STATUS_UPDATED_TOPIC = "transaction.status.updated"; + + @Override + @KafkaListener(topics = TRANSACTION_CREATED_TOPIC) + public void listen(ConsumerRecord record) { + String transactionId = record.value(); + Mono transactionMono = transactionRepository.findById(transactionId); + + transactionMono.subscribe(transaction -> { + if (transaction != null) { + if (transaction.getValue() > 1000) { + kafkaTemplate.send(TRANSACTION_STATUS_UPDATED_TOPIC, transactionId, TransactionStatus.RECHAZADO.toString()); + } else { + kafkaTemplate.send(TRANSACTION_STATUS_UPDATED_TOPIC, transactionId, TransactionStatus.APROBADO.toString()); + } + } else { + kafkaTemplate.send(TRANSACTION_STATUS_UPDATED_TOPIC, transactionId, Constante.TRANSACTION_FOUNT); + } + }, error -> { + kafkaTemplate.send(TRANSACTION_STATUS_UPDATED_TOPIC, transactionId, Constante.TRANSACTION_ERROR); + }); + } +} diff --git a/src/main/java/com/interbank/challenge/service/TransactionService.java b/src/main/java/com/interbank/challenge/service/TransactionService.java index e27c79c..e6d7b70 100644 --- a/src/main/java/com/interbank/challenge/service/TransactionService.java +++ b/src/main/java/com/interbank/challenge/service/TransactionService.java @@ -1,12 +1,13 @@ package com.interbank.challenge.service; import com.interbank.challenge.entity.TransactionDocument; +import org.apache.kafka.clients.consumer.ConsumerRecord; import reactor.core.publisher.Mono; public interface TransactionService { Mono createTransaction(TransactionDocument transaction) ; - Mono updateTransactionStatus(String transactionId, String status); + void updateTransactionStatus(ConsumerRecord record); Mono getTransactionById(String transactionId); } diff --git a/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java index 49c4d5b..30a5bc3 100644 --- a/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java +++ b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java @@ -2,7 +2,10 @@ import com.interbank.challenge.entity.TransactionDocument; import com.interbank.challenge.reporitory.TransactionRepository; +import com.interbank.challenge.util.TransactionStatus; +import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @@ -14,38 +17,39 @@ public class TransactionServiceImpl implements TransactionService{ @Autowired TransactionRepository transactionRepository; - private final String TOPIC = "registro-events"; + private static final String TRANSACTION_CREATED_TOPIC = "transaction.created"; + private static final String TRANSACTION_STATUS_UPDATED_TOPIC = "transaction.status.updated"; @Autowired private KafkaTemplate kafkaTemplate; - public Mono createTransaction(TransactionDocument transaction) { - transaction.setTransactionStatus("pendiente"); - transaction.setCreatedAt(LocalDate.now()); - - System.out.println("preparando para enviar mensaje kafka"); - kafkaTemplate.send(TOPIC, "registro de operacion"); - System.out.println("fin mensaje kafka"); - - return transactionRepository.save(transaction); - + @Override + public Mono createTransaction(TransactionDocument transactionDocument) { + transactionDocument.setTransactionStatus(TransactionStatus.PENDIENTE.toString()); + transactionDocument.setCreatedAt(LocalDate.now()); + return transactionRepository.save(transactionDocument) + .doOnSuccess(transaction -> kafkaTemplate.send(TRANSACTION_CREATED_TOPIC, transaction.getId())); } - public Mono updateTransactionStatus(String transactionId, String status) { - return transactionRepository.findById(transactionId) - .switchIfEmpty(Mono.error(new RuntimeException("Transaction not found"))) + @Override + @KafkaListener(topics = TRANSACTION_STATUS_UPDATED_TOPIC) + public void updateTransactionStatus(ConsumerRecord record) { + String transactionId = record.key(); + String estado = record.value(); + TransactionStatus transactionStatus = TransactionStatus.fromString(estado); + transactionRepository.findById(transactionId) .flatMap(transaction -> { - transaction.setTransactionStatus(status); - return transactionRepository.save(transaction) - .then(Mono.fromRunnable(() -> { - kafkaTemplate.send(TOPIC, "Transaction status updated: " + transactionId + " to " + status); - })); + transaction.setTransactionStatus(transactionStatus.toString()); + return transactionRepository.save(transaction); }) - .then(); + .doOnError(error -> System.err.println("Error al actualizar la transacción: " + error.getMessage())) + .doOnSuccess(transaction -> System.out.println("Transacción actualizada con éxito.")) + .subscribe(); } + @Override public Mono getTransactionById(String transactionId) { return transactionRepository.findById(transactionId) .switchIfEmpty(Mono.error(new RuntimeException("Lista Vacia"))); diff --git a/src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java b/src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java index 34e03b7..2ba1db0 100644 --- a/src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java +++ b/src/main/java/com/interbank/challenge/service/UsuarioServiceImpl.java @@ -1,5 +1,4 @@ package com.interbank.challenge.service; -; import com.interbank.challenge.entity.UsuarioDocument; import com.interbank.challenge.reporitory.RoleRepository; import com.interbank.challenge.reporitory.UsuarioRepository; diff --git a/src/main/java/com/interbank/challenge/util/Constante.java b/src/main/java/com/interbank/challenge/util/Constante.java new file mode 100644 index 0000000..a55a196 --- /dev/null +++ b/src/main/java/com/interbank/challenge/util/Constante.java @@ -0,0 +1,11 @@ +package com.interbank.challenge.util; + +public class Constante { + + public static final String INVALID_CREDENTIAL = "Invalid credentials"; + public static final String BEARER = "Bearer"; + public static final String SERVER_KAFKA = "localhost:9092"; + public static final String MY_GROUP = "myGroup"; + public static final String TRANSACTION_FOUNT = "not_found"; + public static final String TRANSACTION_ERROR= "error"; +} diff --git a/src/main/java/com/interbank/challenge/util/Constantes.java b/src/main/java/com/interbank/challenge/util/Constantes.java deleted file mode 100644 index efaa9e7..0000000 --- a/src/main/java/com/interbank/challenge/util/Constantes.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.interbank.challenge.util; - -public class Constantes { - - public static final String HTTP_401 = "401"; - public static final String INVALID_CREDENTIAL = "Invalid credentials"; - public static final String BEARER = "Bearer"; -} diff --git a/src/main/java/com/interbank/challenge/util/TransactionStatus.java b/src/main/java/com/interbank/challenge/util/TransactionStatus.java new file mode 100644 index 0000000..7c036ec --- /dev/null +++ b/src/main/java/com/interbank/challenge/util/TransactionStatus.java @@ -0,0 +1,32 @@ +package com.interbank.challenge.util; + + +public enum TransactionStatus { + PENDIENTE("pendiente"), + APROBADO("aprobado"), + RECHAZADO("rechazado"); + + private final String displayName; + + TransactionStatus(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public String toString() { + return displayName; + } + + public static TransactionStatus fromString(String status) { + for (TransactionStatus ts : TransactionStatus.values()) { + if (ts.displayName.equalsIgnoreCase(status)) { + return ts; + } + } + throw new IllegalArgumentException("Unknown status: " + status); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bdb21a5..bb7b15e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -35,6 +35,7 @@ logging: springframework: web: info security: DEBUG + kafka: DEBUG hibernate: error springdoc: diff --git a/target/classes/application.yml b/target/classes/application.yml index bdb21a5..bb7b15e 100644 --- a/target/classes/application.yml +++ b/target/classes/application.yml @@ -35,6 +35,7 @@ logging: springframework: web: info security: DEBUG + kafka: DEBUG hibernate: error springdoc: From 3c19e9e6ad09a4c24ceb04156bebc20ef7354fd9 Mon Sep 17 00:00:00 2001 From: Jemn Date: Tue, 6 Aug 2024 22:09:37 -0500 Subject: [PATCH 06/14] manejo de DTO y Mapper para seguridad de los controller --- pom.xml | 11 ++++++ .../controller/TransactionController.java | 3 +- .../dto/request/TransactionRequest.java | 38 +++++++++++++++++++ .../challenge/service/TransactionService.java | 3 +- .../service/TransactionServiceImpl.java | 10 +++-- .../challenge/util/TransactionMapper.java | 29 ++++++++++++++ 6 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/interbank/challenge/entity/dto/request/TransactionRequest.java create mode 100644 src/main/java/com/interbank/challenge/util/TransactionMapper.java diff --git a/pom.xml b/pom.xml index db68183..0960cda 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,17 @@ springdoc-openapi-starter-webflux-api 2.6.0 + + org.mapstruct + mapstruct + 1.5.3.Final + + + org.mapstruct + mapstruct-processor + 1.5.3.Final + provided + diff --git a/src/main/java/com/interbank/challenge/controller/TransactionController.java b/src/main/java/com/interbank/challenge/controller/TransactionController.java index 1568fee..a09a052 100644 --- a/src/main/java/com/interbank/challenge/controller/TransactionController.java +++ b/src/main/java/com/interbank/challenge/controller/TransactionController.java @@ -1,6 +1,7 @@ package com.interbank.challenge.controller; import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.entity.dto.request.TransactionRequest; import com.interbank.challenge.service.TransactionService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; @@ -17,7 +18,7 @@ public class TransactionController { @PostMapping("/save") @Operation(summary = "Metodo createTransaction", description = "Crea una operación") - public Mono createTransaction(@RequestHeader("Authorization") String token, @Valid @RequestBody TransactionDocument transaction) { + public Mono createTransaction(@RequestHeader("Authorization") String token, @Valid @RequestBody TransactionRequest transaction) { return transactionService.createTransaction(transaction); } diff --git a/src/main/java/com/interbank/challenge/entity/dto/request/TransactionRequest.java b/src/main/java/com/interbank/challenge/entity/dto/request/TransactionRequest.java new file mode 100644 index 0000000..af3c70b --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/request/TransactionRequest.java @@ -0,0 +1,38 @@ +package com.interbank.challenge.entity.dto.request; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +@Data +public class TransactionRequest { + + @Size(min = 36, max = 36) + private String id; + + @NotNull + @Size(min = 36, max = 36) + private String accountDebit; + + @NotNull + @Size(min = 36, max = 36) + private String accountCredit; + + @NotNull + @Positive + private Integer type; + + @NotNull + @Positive + private Integer values; + + private String status; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate date; + +} diff --git a/src/main/java/com/interbank/challenge/service/TransactionService.java b/src/main/java/com/interbank/challenge/service/TransactionService.java index e6d7b70..8c9d8cc 100644 --- a/src/main/java/com/interbank/challenge/service/TransactionService.java +++ b/src/main/java/com/interbank/challenge/service/TransactionService.java @@ -1,12 +1,13 @@ package com.interbank.challenge.service; import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.entity.dto.request.TransactionRequest; import org.apache.kafka.clients.consumer.ConsumerRecord; import reactor.core.publisher.Mono; public interface TransactionService { - Mono createTransaction(TransactionDocument transaction) ; + Mono createTransaction(TransactionRequest transaction) ; void updateTransactionStatus(ConsumerRecord record); Mono getTransactionById(String transactionId); diff --git a/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java index 30a5bc3..45ca63c 100644 --- a/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java +++ b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java @@ -1,7 +1,9 @@ package com.interbank.challenge.service; import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.entity.dto.request.TransactionRequest; import com.interbank.challenge.reporitory.TransactionRepository; +import com.interbank.challenge.util.TransactionMapper; import com.interbank.challenge.util.TransactionStatus; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.beans.factory.annotation.Autowired; @@ -19,18 +21,20 @@ public class TransactionServiceImpl implements TransactionService{ private static final String TRANSACTION_CREATED_TOPIC = "transaction.created"; private static final String TRANSACTION_STATUS_UPDATED_TOPIC = "transaction.status.updated"; + private final TransactionMapper transactionMapper = TransactionMapper.INSTANCE; @Autowired private KafkaTemplate kafkaTemplate; @Override - public Mono createTransaction(TransactionDocument transactionDocument) { - + public Mono createTransaction(TransactionRequest transactionRequest) { + TransactionDocument transactionDocument = transactionMapper.toTransactionDocument(transactionRequest); transactionDocument.setTransactionStatus(TransactionStatus.PENDIENTE.toString()); transactionDocument.setCreatedAt(LocalDate.now()); return transactionRepository.save(transactionDocument) - .doOnSuccess(transaction -> kafkaTemplate.send(TRANSACTION_CREATED_TOPIC, transaction.getId())); + .doOnSuccess(transaction -> kafkaTemplate.send(TRANSACTION_CREATED_TOPIC, transaction.getId())) + .map(transactionMapper::toTransactionRequest); } @Override diff --git a/src/main/java/com/interbank/challenge/util/TransactionMapper.java b/src/main/java/com/interbank/challenge/util/TransactionMapper.java new file mode 100644 index 0000000..a5fe3a6 --- /dev/null +++ b/src/main/java/com/interbank/challenge/util/TransactionMapper.java @@ -0,0 +1,29 @@ +package com.interbank.challenge.util; + +import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.entity.dto.request.TransactionRequest; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface TransactionMapper { + + TransactionMapper INSTANCE = Mappers.getMapper(TransactionMapper.class); + + @Mapping(source = "accountDebit", target = "accountExternalIdDebit") + @Mapping(source = "accountCredit", target = "accountExternalIdCredit") + @Mapping(source = "type", target = "tranferTypeId") + @Mapping(source = "values", target = "value") + @Mapping(source = "status", target = "transactionStatus") + @Mapping(source = "date", target = "createdAt") + TransactionDocument toTransactionDocument(TransactionRequest request); + + @Mapping(source = "accountExternalIdDebit", target = "accountDebit") + @Mapping(source = "accountExternalIdCredit", target = "accountCredit") + @Mapping(source = "tranferTypeId", target = "type") + @Mapping(source = "value", target = "values") + @Mapping(source = "transactionStatus", target = "status") + @Mapping(source = "createdAt", target = "date") + TransactionRequest toTransactionRequest(TransactionDocument document); +} From 53d29158f35918f3f09e0293f4f682c54e5e3e28 Mon Sep 17 00:00:00 2001 From: Jemn Date: Tue, 6 Aug 2024 23:19:02 -0500 Subject: [PATCH 07/14] mejora de controller --- .../controller/TransactionController.java | 18 +++++++--- .../dto/response/TransactionResponse.java | 13 +++++++ .../entity/dto/response/TransactionType.java | 8 +++++ .../entity/dto/response/TypeStatus.java | 8 +++++ .../{util => mapper}/TransactionMapper.java | 2 +- .../challenge/service/TransactionService.java | 3 +- .../service/TransactionServiceImpl.java | 35 +++++++++++++++++-- 7 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/interbank/challenge/entity/dto/response/TransactionResponse.java create mode 100644 src/main/java/com/interbank/challenge/entity/dto/response/TransactionType.java create mode 100644 src/main/java/com/interbank/challenge/entity/dto/response/TypeStatus.java rename src/main/java/com/interbank/challenge/{util => mapper}/TransactionMapper.java (96%) diff --git a/src/main/java/com/interbank/challenge/controller/TransactionController.java b/src/main/java/com/interbank/challenge/controller/TransactionController.java index a09a052..66e7c34 100644 --- a/src/main/java/com/interbank/challenge/controller/TransactionController.java +++ b/src/main/java/com/interbank/challenge/controller/TransactionController.java @@ -1,14 +1,18 @@ package com.interbank.challenge.controller; -import com.interbank.challenge.entity.TransactionDocument; import com.interbank.challenge.entity.dto.request.TransactionRequest; +import com.interbank.challenge.entity.dto.response.TransactionResponse; import com.interbank.challenge.service.TransactionService; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; +import java.util.HashMap; +import java.util.Map; + @RestController @RequestMapping("/api/transactions") public class TransactionController { @@ -18,14 +22,20 @@ public class TransactionController { @PostMapping("/save") @Operation(summary = "Metodo createTransaction", description = "Crea una operación") - public Mono createTransaction(@RequestHeader("Authorization") String token, @Valid @RequestBody TransactionRequest transaction) { - return transactionService.createTransaction(transaction); + public Mono>> createTransaction(@RequestHeader("Authorization") String token, @Valid @RequestBody TransactionRequest transaction) { + return transactionService.createTransaction(transaction) + .map(transactionId -> { + Map response = new HashMap<>(); + response.put("idTransaction", transactionId.getId()); + response.put("message", "El registro se hizo correctamente"); + return ResponseEntity.ok(response); + }); } @GetMapping("/list/{id}") @Operation(summary = "Metodo getTransaction", description = "Devuelve una operacion") - public Mono getTransaction(@RequestHeader("Authorization") String token, @Valid @PathVariable String id) { + public Mono getTransaction(@RequestHeader("Authorization") String token, @Valid @PathVariable String id) { return transactionService.getTransactionById(id); } } diff --git a/src/main/java/com/interbank/challenge/entity/dto/response/TransactionResponse.java b/src/main/java/com/interbank/challenge/entity/dto/response/TransactionResponse.java new file mode 100644 index 0000000..3a40f30 --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/response/TransactionResponse.java @@ -0,0 +1,13 @@ +package com.interbank.challenge.entity.dto.response; + +import lombok.Data; + +@Data +public class TransactionResponse { + private String transactionId; + private TransactionType type; + private TypeStatus status; + private Integer value; + private String date; + +} diff --git a/src/main/java/com/interbank/challenge/entity/dto/response/TransactionType.java b/src/main/java/com/interbank/challenge/entity/dto/response/TransactionType.java new file mode 100644 index 0000000..490023a --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/response/TransactionType.java @@ -0,0 +1,8 @@ +package com.interbank.challenge.entity.dto.response; + +import lombok.Data; + +@Data +public class TransactionType { + private String name; +} diff --git a/src/main/java/com/interbank/challenge/entity/dto/response/TypeStatus.java b/src/main/java/com/interbank/challenge/entity/dto/response/TypeStatus.java new file mode 100644 index 0000000..91fff31 --- /dev/null +++ b/src/main/java/com/interbank/challenge/entity/dto/response/TypeStatus.java @@ -0,0 +1,8 @@ +package com.interbank.challenge.entity.dto.response; + +import lombok.Data; + +@Data +public class TypeStatus { + private String name; +} diff --git a/src/main/java/com/interbank/challenge/util/TransactionMapper.java b/src/main/java/com/interbank/challenge/mapper/TransactionMapper.java similarity index 96% rename from src/main/java/com/interbank/challenge/util/TransactionMapper.java rename to src/main/java/com/interbank/challenge/mapper/TransactionMapper.java index a5fe3a6..1808539 100644 --- a/src/main/java/com/interbank/challenge/util/TransactionMapper.java +++ b/src/main/java/com/interbank/challenge/mapper/TransactionMapper.java @@ -1,4 +1,4 @@ -package com.interbank.challenge.util; +package com.interbank.challenge.mapper; import com.interbank.challenge.entity.TransactionDocument; import com.interbank.challenge.entity.dto.request.TransactionRequest; diff --git a/src/main/java/com/interbank/challenge/service/TransactionService.java b/src/main/java/com/interbank/challenge/service/TransactionService.java index 8c9d8cc..8ebf18a 100644 --- a/src/main/java/com/interbank/challenge/service/TransactionService.java +++ b/src/main/java/com/interbank/challenge/service/TransactionService.java @@ -2,6 +2,7 @@ import com.interbank.challenge.entity.TransactionDocument; import com.interbank.challenge.entity.dto.request.TransactionRequest; +import com.interbank.challenge.entity.dto.response.TransactionResponse; import org.apache.kafka.clients.consumer.ConsumerRecord; import reactor.core.publisher.Mono; @@ -9,6 +10,6 @@ public interface TransactionService { Mono createTransaction(TransactionRequest transaction) ; void updateTransactionStatus(ConsumerRecord record); - Mono getTransactionById(String transactionId); + Mono getTransactionById(String transactionId); } diff --git a/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java index 45ca63c..daa0a26 100644 --- a/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java +++ b/src/main/java/com/interbank/challenge/service/TransactionServiceImpl.java @@ -2,8 +2,11 @@ import com.interbank.challenge.entity.TransactionDocument; import com.interbank.challenge.entity.dto.request.TransactionRequest; +import com.interbank.challenge.entity.dto.response.TransactionResponse; +import com.interbank.challenge.entity.dto.response.TransactionType; +import com.interbank.challenge.entity.dto.response.TypeStatus; +import com.interbank.challenge.mapper.TransactionMapper; import com.interbank.challenge.reporitory.TransactionRepository; -import com.interbank.challenge.util.TransactionMapper; import com.interbank.challenge.util.TransactionStatus; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.beans.factory.annotation.Autowired; @@ -54,8 +57,34 @@ public void updateTransactionStatus(ConsumerRecord record) { } @Override - public Mono getTransactionById(String transactionId) { + public Mono getTransactionById(String transactionId) { return transactionRepository.findById(transactionId) - .switchIfEmpty(Mono.error(new RuntimeException("Lista Vacia"))); + .map(transaction -> { + TransactionResponse response = new TransactionResponse(); + response.setTransactionId(transaction.getId()); + response.setValue(transaction.getValue()); + response.setDate(transaction.getCreatedAt().toString()); + + TransactionType type = new TransactionType(); + type.setName(getTransactionTypeName(transaction.getTranferTypeId())); + response.setType(type); + + TransactionStatus transactionStatus = TransactionStatus.fromString(transaction.getTransactionStatus()); + + TypeStatus typeStatus = new TypeStatus(); + typeStatus.setName(transactionStatus.toString()); + response.setStatus(typeStatus); + + return response; + }) + .switchIfEmpty(Mono.error(new RuntimeException("Transacción no encontrada"))); + } + + private String getTransactionTypeName(Integer typeId) { + switch (typeId) { + case 1: return "debido"; + case 2: return "credito"; + default: return "Unknown"; + } } } From a35623d11488998d2fecd1b88308459a1664d63f Mon Sep 17 00:00:00 2001 From: Jemn Date: Tue, 6 Aug 2024 23:23:45 -0500 Subject: [PATCH 08/14] update api-doc.yaml --- .../challenge/config/SecurityConfig.java | 1 - .../challenge/controller/LoginController.java | 21 ---- .../resources/documentacion/api-docs.yaml | 106 ++++++++++++++++-- 3 files changed, 94 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/interbank/challenge/config/SecurityConfig.java b/src/main/java/com/interbank/challenge/config/SecurityConfig.java index cd7c2cc..cc2e30b 100644 --- a/src/main/java/com/interbank/challenge/config/SecurityConfig.java +++ b/src/main/java/com/interbank/challenge/config/SecurityConfig.java @@ -33,7 +33,6 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, Jw "/v3/api-docs.yaml", "/api/login/signin", "/api/login/signup", - "/api/login/listuser", "/api/transactions/save", "/api/transactions/list/*").permitAll() .anyExchange().authenticated() diff --git a/src/main/java/com/interbank/challenge/controller/LoginController.java b/src/main/java/com/interbank/challenge/controller/LoginController.java index 02bcaa5..ce6ab44 100644 --- a/src/main/java/com/interbank/challenge/controller/LoginController.java +++ b/src/main/java/com/interbank/challenge/controller/LoginController.java @@ -83,25 +83,4 @@ public Mono> saveUser(@RequestBody UsuarioDocume ); } - @PostMapping("/listuser") - @Operation(summary = "Metodo listuser", description = "Lista de Usuario en la base de datos") - public Mono> listuser(@RequestHeader("Authorization") String token, @Valid @RequestBody SigninRequest signinReques) { - - if (token.startsWith("Bearer ")) { - token = token.substring(7); - } - - if (jwtUtil.validateToken(token, signinReques.getUsername())) { - return Mono.just(ResponseEntity.status(HttpStatus.OK).body(MessageResponse.builder() - .codeResponse(HttpStatus.OK.value()) - .messageResponse("Successful registration") - .build())); - } else { - return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(MessageResponse.builder() - .codeResponse(HttpStatus.INTERNAL_SERVER_ERROR.value()) - .messageResponse("Service error") - .build())); - } - } - } \ No newline at end of file diff --git a/src/main/resources/documentacion/api-docs.yaml b/src/main/resources/documentacion/api-docs.yaml index 075b9cc..b6cb378 100644 --- a/src/main/resources/documentacion/api-docs.yaml +++ b/src/main/resources/documentacion/api-docs.yaml @@ -6,6 +6,34 @@ servers: - url: http://localhost:8082 description: Generated server url paths: + /api/transactions/save: + post: + tags: + - transaction-controller + summary: Metodo createTransaction + description: Crea una operación + operationId: createTransaction + parameters: + - name: Authorization + in: header + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TransactionRequest" + required: true + responses: + "200": + description: OK + content: + '*/*': + schema: + type: object + additionalProperties: + type: string /api/login/signup: post: tags: @@ -46,34 +74,64 @@ paths: '*/*': schema: type: object - /api/login/listuser: - post: + /api/transactions/list/{id}: + get: tags: - - login-controller - summary: Metodo listuser - description: Lista de Usuario en la base de datos - operationId: listuser + - transaction-controller + summary: Metodo getTransaction + description: Devuelve una operacion + operationId: getTransaction parameters: - name: Authorization in: header required: true schema: type: string - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/SigninRequest" + - name: id + in: path required: true + schema: + type: string responses: "200": description: OK content: '*/*': schema: - type: object + $ref: "#/components/schemas/TransactionResponse" components: schemas: + TransactionRequest: + required: + - accountCredit + - accountDebit + - type + - values + type: object + properties: + id: + maxLength: 36 + minLength: 36 + type: string + accountDebit: + maxLength: 36 + minLength: 36 + type: string + accountCredit: + maxLength: 36 + minLength: 36 + type: string + type: + type: integer + format: int32 + values: + type: integer + format: int32 + status: + type: string + date: + type: string + format: date RoleDocument: type: object properties: @@ -119,3 +177,27 @@ components: type: string password: type: string + TransactionResponse: + type: object + properties: + transactionId: + type: string + type: + $ref: "#/components/schemas/TransactionType" + status: + $ref: "#/components/schemas/TypeStatus" + value: + type: integer + format: int32 + date: + type: string + TransactionType: + type: object + properties: + name: + type: string + TypeStatus: + type: object + properties: + name: + type: string From d7c6093abce3a4440d7987e49b97bb25670825fa Mon Sep 17 00:00:00 2001 From: Jemn Date: Tue, 6 Aug 2024 23:26:26 -0500 Subject: [PATCH 09/14] se agrega collections to postman --- .../Interbank.postman_collection.json | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/main/resources/collection-postman/Interbank.postman_collection.json diff --git a/src/main/resources/collection-postman/Interbank.postman_collection.json b/src/main/resources/collection-postman/Interbank.postman_collection.json new file mode 100644 index 0000000..6a9c139 --- /dev/null +++ b/src/main/resources/collection-postman/Interbank.postman_collection.json @@ -0,0 +1,170 @@ +{ + "info": { + "_postman_id": "c6f08e19-66f6-43fe-b62e-dfd6f98a76dc", + "name": "Interbank", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "9325910", + "_collection_link": "https://lively-moon-301039.postman.co/workspace/PROY-PROYECTOS~f3143bb4-021d-48c7-8824-9d431947399d/collection/9325910-c6f08e19-66f6-43fe-b62e-dfd6f98a76dc?action=share&source=collection_link&creator=9325910" + }, + "item": [ + { + "name": "api-challenge-ms", + "item": [ + { + "name": "http://localhost:8082/api/login/signin - obtener token", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"prueba\",\r\n \"password\": \"prueba123\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8082/api/login/signin", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8082", + "path": [ + "api", + "login", + "signin" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8082/api/transactions/save - rechazado", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"accountDebit\": \"136e4567-e89b-12d3-a456-426614174036\",\r\n \"accountCredit\": \"136e4567-e89b-12d3-a456-426614174036\",\r\n \"type\": 2,\r\n \"values\": 1200\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8082/api/transactions/save", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8082", + "path": [ + "api", + "transactions", + "save" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8082/api/transactions/save - aprobado", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"accountDebit\": \"136e4567-e89b-12d3-a456-426614174036\",\r\n \"accountCredit\": \"136e4567-e89b-12d3-a456-426614174036\",\r\n \"type\": 2,\r\n \"values\": 900\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8082/api/transactions/save", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8082", + "path": [ + "api", + "transactions", + "save" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8082/api/transactions/list", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8082/api/transactions/list/66b2f65a83fe750de3db4986", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8082", + "path": [ + "api", + "transactions", + "list", + "66b2f65a83fe750de3db4986" + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file From 702de3da33e963135138b7a7a25dc8d079965f87 Mon Sep 17 00:00:00 2001 From: Jemn Date: Wed, 7 Aug 2024 00:25:59 -0500 Subject: [PATCH 10/14] se completa los test del servicio con Junit 5 --- .../controller/LoginControllerTest.java | 98 +++++++++++++++++++ .../controller/TransactionControllerTest.java | 62 ++++++++++++ .../service/AntifraudeServiceImplTest.java | 70 +++++++++++++ .../service/TransactionServiceImpl.java | 79 +++++++++++++++ .../challenge/service/UsuarioServiceImpl.java | 68 +++++++++++++ 5 files changed, 377 insertions(+) create mode 100644 src/test/java/com/interbank/challenge/controller/LoginControllerTest.java create mode 100644 src/test/java/com/interbank/challenge/controller/TransactionControllerTest.java create mode 100644 src/test/java/com/interbank/challenge/service/AntifraudeServiceImplTest.java create mode 100644 src/test/java/com/interbank/challenge/service/TransactionServiceImpl.java create mode 100644 src/test/java/com/interbank/challenge/service/UsuarioServiceImpl.java diff --git a/src/test/java/com/interbank/challenge/controller/LoginControllerTest.java b/src/test/java/com/interbank/challenge/controller/LoginControllerTest.java new file mode 100644 index 0000000..24f9bc1 --- /dev/null +++ b/src/test/java/com/interbank/challenge/controller/LoginControllerTest.java @@ -0,0 +1,98 @@ +package com.interbank.challenge.controller; + +import com.interbank.challenge.config.jwt.JwtUtil; +import com.interbank.challenge.entity.RoleDocument; +import com.interbank.challenge.entity.UsuarioDocument; +import com.interbank.challenge.entity.dto.request.SigninRequest; +import com.interbank.challenge.entity.dto.response.MessageResponse; +import com.interbank.challenge.entity.dto.response.SigninResponse; +import com.interbank.challenge.service.UsuarioService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +class LoginControllerTest { + + @InjectMocks + private LoginController loginController; + + @Mock + private JwtUtil jwtUtil; + + @Mock + private UsuarioService usuarioService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("crear un token - debe devolver token") + void createAuthenticationToken_ShouldReturnToken() { + SigninRequest signinRequest = new SigninRequest(); + signinRequest.setUsername("prueba"); + signinRequest.setPassword("prueba123"); + + UsuarioDocument user = new UsuarioDocument(); + user.setId("testId"); + user.setUsername(signinRequest.getUsername()); + user.setPassword("$2a$10$7d1E6Pe0Uv5A2l7OQ2iF/OuK4a1yLXwh56o5jGTHkA5Vijyz/ZGSO"); + user.setEmail("prueba@example.com"); + user.setLocked(false); + user.setDisable(false); + + RoleDocument role = new RoleDocument(); + role.setRole("USER_ADMIN"); + user.setRoleDocument(role); + + when(usuarioService.findByUsername(signinRequest.getUsername())).thenReturn(Mono.just(user)); + when(usuarioService.comparePasswords(signinRequest.getPassword(), user.getPassword())).thenReturn(true); + when(jwtUtil.generateToken(signinRequest.getUsername())).thenReturn("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI"); + + ResponseEntity responseEntity = loginController.createAuthenticationToken(signinRequest).block(); + + assertNotNull(responseEntity); + assertInstanceOf(SigninResponse.class, responseEntity.getBody()); + + SigninResponse signinResponse = (SigninResponse) responseEntity.getBody(); + assertEquals("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI", signinResponse.getAccessToken()); + assertEquals("Bearer", signinResponse.getTokenType()); + assertEquals("testId", signinResponse.getId()); + assertEquals("prueba", signinResponse.getUsername()); + assertEquals("prueba@example.com", signinResponse.getEmail()); + assertFalse(signinResponse.getLocked()); + assertFalse(signinResponse.getDisable()); + assertEquals("USER_ADMIN", signinResponse.getRole()); + } + + @Test + @DisplayName("Guarda usuario - devuelve mensaje regsitro correcto") + void saveUser_ShouldReturnSuccessMessage() { + UsuarioDocument user = new UsuarioDocument(); + + user.setUsername("testUser"); + user.setPassword("testPassword"); + + when(usuarioService.findByUsername(anyString())).thenReturn(Mono.empty()); + when(usuarioService.saveUser(any(UsuarioDocument.class))).thenReturn(Mono.just(user)); + + ResponseEntity responseEntity = loginController.saveUser(user).block(); + + assertNotNull(responseEntity); + assertInstanceOf(MessageResponse.class, responseEntity.getBody()); + + MessageResponse messageResponse = (MessageResponse) responseEntity.getBody(); + assertEquals("Successful registration", messageResponse.getMessageResponse()); + } +} diff --git a/src/test/java/com/interbank/challenge/controller/TransactionControllerTest.java b/src/test/java/com/interbank/challenge/controller/TransactionControllerTest.java new file mode 100644 index 0000000..b156beb --- /dev/null +++ b/src/test/java/com/interbank/challenge/controller/TransactionControllerTest.java @@ -0,0 +1,62 @@ +package com.interbank.challenge.controller; + +import com.interbank.challenge.entity.dto.request.TransactionRequest; +import com.interbank.challenge.entity.dto.response.TransactionResponse; +import com.interbank.challenge.service.TransactionService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Mono; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class TransactionControllerTest { + + @InjectMocks + private TransactionController transactionController; + + @Mock + private TransactionService transactionService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("Crea una Transaccion - retorna mensage exitoso") + void createTransaction_ShouldReturnSuccessMessage() { + TransactionRequest transactionRequest = new TransactionRequest(); + + when(transactionService.createTransaction(any(TransactionRequest.class))) + .thenReturn(Mono.just(transactionRequest)); + + ResponseEntity> responseEntity = transactionController + .createTransaction("Bearer token", transactionRequest) + .block(); + + assertEquals("El registro se hizo correctamente", responseEntity.getBody().get("message")); + } + + @Test + @DisplayName("obtiene una transaccion") + void getTransaction_ShouldReturnTransactionResponse() { + String transactionId = "123"; + TransactionResponse response = new TransactionResponse(); + + when(transactionService.getTransactionById(transactionId)) + .thenReturn(Mono.just(response)); + + TransactionResponse result = transactionController.getTransaction("Bearer token", transactionId).block(); + + assertEquals(response, result); + } +} diff --git a/src/test/java/com/interbank/challenge/service/AntifraudeServiceImplTest.java b/src/test/java/com/interbank/challenge/service/AntifraudeServiceImplTest.java new file mode 100644 index 0000000..c36ffb2 --- /dev/null +++ b/src/test/java/com/interbank/challenge/service/AntifraudeServiceImplTest.java @@ -0,0 +1,70 @@ +package com.interbank.challenge.service; + +import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.reporitory.TransactionRepository; +import com.interbank.challenge.util.TransactionStatus; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.kafka.core.KafkaTemplate; +import reactor.core.publisher.Mono; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +class AntifraudeServiceImplTest { + + @InjectMocks + private AntifraudeServiceImpl antifraudeService; + + @Mock + private KafkaTemplate kafkaTemplate; + + @Mock + private TransactionRepository transactionRepository; + + private static final String TRANSACTION_CREATED_TOPIC = "transaction.created"; + private static final String TRANSACTION_STATUS_UPDATED_TOPIC = "transaction.status.updated"; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("Transaccion aprobada cuando el valor es menor a mil") + void listen_ShouldApproveTransaction_WhenValueIsLessThanThreshold() { + String transactionId = "123"; + ConsumerRecord record = new ConsumerRecord<>(TRANSACTION_CREATED_TOPIC, 0, 0, null, transactionId); + + TransactionDocument transaction = new TransactionDocument(); + transaction.setValue(500); + when(transactionRepository.findById(transactionId)).thenReturn(Mono.just(transaction)); + + antifraudeService.listen(record); + + verify(kafkaTemplate, times(1)) + .send(eq(TRANSACTION_STATUS_UPDATED_TOPIC), eq(transactionId), eq(TransactionStatus.APROBADO.toString())); + } + + @Test + @DisplayName("Transaccion rechazada cuando el valor es mayor a mil") + void listen_ShouldRejectTransaction_WhenValueIsGreaterThanThreshold() { + String transactionId = "123"; + ConsumerRecord record = new ConsumerRecord<>(TRANSACTION_CREATED_TOPIC, 0, 0, null, transactionId); + + TransactionDocument transaction = new TransactionDocument(); + transaction.setValue(1500); + when(transactionRepository.findById(transactionId)).thenReturn(Mono.just(transaction)); + + antifraudeService.listen(record); + + verify(kafkaTemplate, times(1)) + .send(eq(TRANSACTION_STATUS_UPDATED_TOPIC), eq(transactionId), eq(TransactionStatus.RECHAZADO.toString())); + } + +} \ No newline at end of file diff --git a/src/test/java/com/interbank/challenge/service/TransactionServiceImpl.java b/src/test/java/com/interbank/challenge/service/TransactionServiceImpl.java new file mode 100644 index 0000000..2b97ad3 --- /dev/null +++ b/src/test/java/com/interbank/challenge/service/TransactionServiceImpl.java @@ -0,0 +1,79 @@ +package com.interbank.challenge.service; + +import com.interbank.challenge.entity.TransactionDocument; +import com.interbank.challenge.entity.dto.request.TransactionRequest; +import com.interbank.challenge.entity.dto.response.TransactionResponse; +import com.interbank.challenge.mapper.TransactionMapper; +import com.interbank.challenge.reporitory.TransactionRepository; +import com.interbank.challenge.util.TransactionStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.kafka.core.KafkaTemplate; +import reactor.core.publisher.Mono; +import java.time.LocalDate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class TransactionServiceImplTest { + + @InjectMocks + private TransactionServiceImpl transactionService; + + @Mock + private TransactionRepository transactionRepository; + + @Mock + private KafkaTemplate kafkaTemplate; + + private final TransactionMapper transactionMapper = TransactionMapper.INSTANCE; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("Crea una Transaccion - retorna mensage exitoso") + void createTransaction_ShouldReturnTransactionRequest() { + TransactionRequest request = new TransactionRequest(); + + TransactionDocument document = transactionMapper.toTransactionDocument(request); + when(transactionRepository.save(any(TransactionDocument.class))) + .thenReturn(Mono.just(document)); + + TransactionRequest result = transactionService.createTransaction(request).block(); + + assertEquals(request, result); + } + + @Test + @DisplayName("obtiene una transaccion por ID") + void getTransactionById_ShouldReturnTransactionResponse() { + + TransactionDocument transactionDocument = new TransactionDocument(); + transactionDocument.setId("testTransactionId"); + transactionDocument.setValue(1500); + transactionDocument.setTranferTypeId(1); + transactionDocument.setTransactionStatus(TransactionStatus.PENDIENTE.toString()); + transactionDocument.setCreatedAt(LocalDate.of(2024, 8, 6)); + + when(transactionRepository.findById("testTransactionId")).thenReturn(Mono.just(transactionDocument)); + + Mono transactionResponseMono = transactionService.getTransactionById("testTransactionId");; + + TransactionResponse transactionResponse = transactionResponseMono.block(); + assertNotNull(transactionResponse); + assertEquals("testTransactionId", transactionResponse.getTransactionId()); + assertEquals(1500, transactionResponse.getValue()); + assertEquals("2024-08-06", transactionResponse.getDate()); + assertEquals("debido", transactionResponse.getType().getName()); + assertEquals(TransactionStatus.PENDIENTE.toString(), transactionResponse.getStatus().getName()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/interbank/challenge/service/UsuarioServiceImpl.java b/src/test/java/com/interbank/challenge/service/UsuarioServiceImpl.java new file mode 100644 index 0000000..45fb7a0 --- /dev/null +++ b/src/test/java/com/interbank/challenge/service/UsuarioServiceImpl.java @@ -0,0 +1,68 @@ +package com.interbank.challenge.service; + +import com.interbank.challenge.entity.RoleDocument; +import com.interbank.challenge.entity.UsuarioDocument; +import com.interbank.challenge.reporitory.UsuarioRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class UsuarioServiceImplTest { + + @InjectMocks + private UsuarioServiceImpl usuarioService; + + @Mock + private UsuarioRepository usuarioRepository; + + @Mock + private BCryptPasswordEncoder bCryptPasswordEncoder; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("Guarda Usuario - regsitro exitoso") + void saveUser_ShouldReturnSavedUser() { + UsuarioDocument user = new UsuarioDocument(); + user.setUsername("prueba"); + user.setPassword("prueba123"); + + RoleDocument roleDocument = new RoleDocument(); + roleDocument.setRole("ROLE_ADMIN"); + user.setRoleDocument(roleDocument); + + String encodedPassword = "encodedPassword"; + + when(bCryptPasswordEncoder.encode("prueba123")).thenReturn(encodedPassword); + when(usuarioRepository.save(any(UsuarioDocument.class))).thenReturn(Mono.just(user)); + Mono savedUserMono = usuarioService.saveUser(user); + + UsuarioDocument savedUser = savedUserMono.block(); + assertEquals("prueba", savedUser.getUsername()); + assertEquals(encodedPassword, savedUser.getPassword()); + assertNotNull(savedUser.getRoleDocument()); + assertEquals("ROLE_ADMIN", savedUser.getRoleDocument().getRole()); + } + + @Test + @DisplayName("Validacion de password") + void comparePasswords_ShouldReturnTrue() { + when(bCryptPasswordEncoder.matches(any(), any())).thenReturn(true); + + boolean result = usuarioService.comparePasswords("rawPassword", "encodedPassword"); + + assertTrue(result); + } +} From 5f2bbd6e01ee0c54e0f477805b54b8ad0ff0ceca Mon Sep 17 00:00:00 2001 From: Jemn Date: Wed, 7 Aug 2024 00:40:26 -0500 Subject: [PATCH 11/14] se agrega json para trabajo con mongodb --- .../challenge_prod.table_role.json | 30 +++++++++++++++++++ .../challenge_prod.table_transaction.json | 28 +++++++++++++++++ .../challenge_prod.table_user.json | 20 +++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/main/resources/documento_db/challenge_prod.table_role.json create mode 100644 src/main/resources/documento_db/challenge_prod.table_transaction.json create mode 100644 src/main/resources/documento_db/challenge_prod.table_user.json diff --git a/src/main/resources/documento_db/challenge_prod.table_role.json b/src/main/resources/documento_db/challenge_prod.table_role.json new file mode 100644 index 0000000..fc8159c --- /dev/null +++ b/src/main/resources/documento_db/challenge_prod.table_role.json @@ -0,0 +1,30 @@ +[{ + "_id": { + "$oid": "663e6f02c7e1ec15a4d93e66" + }, + "role": "USER", + "date": { + "$date": "2024-08-02T05:00:00.000Z" + }, + "_class": "com.interbank.challenge.entity.RoleDocum" +}, +{ + "_id": { + "$oid": "663e6f02c7e1ec15a4d93e67" + }, + "role": "ADMIN", + "date": { + "$date": "2024-08-02T05:00:00.000Z" + }, + "_class": "com.interbank.challenge.entity.RoleDocument" +}, +{ + "_id": { + "$oid": "663e6f02c7e1ec15a4d93e68" + }, + "role": "CUSTOMER", + "date": { + "$date": "2024-08-02T05:00:00.000Z" + }, + "_class": "com.estrasys.authentication.entity.RoleDocument" +}] \ No newline at end of file diff --git a/src/main/resources/documento_db/challenge_prod.table_transaction.json b/src/main/resources/documento_db/challenge_prod.table_transaction.json new file mode 100644 index 0000000..aa1d792 --- /dev/null +++ b/src/main/resources/documento_db/challenge_prod.table_transaction.json @@ -0,0 +1,28 @@ +[{ + "_id": { + "$oid": "66b304e8d0879a0d773e7fe3" + }, + "accountExternalIdDebit": "136e4567-e89b-12d3-a456-426614174036", + "accountExternalIdCredit": "136e4567-e89b-12d3-a456-426614174036", + "tranferTypeId": 2, + "value": 1200, + "transactionStatus": "rechazado", + "createdAt": { + "$date": "2024-08-07T05:00:00.000Z" + }, + "_class": "com.interbank.challenge.entity.TransactionDocument" +}, +{ + "_id": { + "$oid": "66b304f8d0879a0d773e7fe4" + }, + "accountExternalIdDebit": "136e4567-e89b-12d3-a456-426614174036", + "accountExternalIdCredit": "136e4567-e89b-12d3-a456-426614174036", + "tranferTypeId": 2, + "value": 900, + "transactionStatus": "aprobado", + "createdAt": { + "$date": "2024-08-07T05:00:00.000Z" + }, + "_class": "com.interbank.challenge.entity.TransactionDocument" +}] \ No newline at end of file diff --git a/src/main/resources/documento_db/challenge_prod.table_user.json b/src/main/resources/documento_db/challenge_prod.table_user.json new file mode 100644 index 0000000..61a7200 --- /dev/null +++ b/src/main/resources/documento_db/challenge_prod.table_user.json @@ -0,0 +1,20 @@ +[{ + "_id": { + "$oid": "66ad7a97a28fbe088fa677f2" + }, + "username": "prueba", + "password": "$2a$10$GROKwgPpk3Bm0pkQs7zlM.1vK9JcP0HKCeunsUyYWYbIbea36Zdmm", + "email": "prueba@hotmail.com", + "locked": true, + "disable": true, + "roleDocument": { + "_id": { + "$oid": "663e6f02c7e1ec15a4d93e67" + }, + "role": "ADMIN", + "date": { + "$date": "2024-08-02T05:00:00.000Z" + } + }, + "_class": "com.interbank.challenge.entity.UsuarioDocument" +}] \ No newline at end of file From f6792b6666aac758205a0ba2a04bccddc1c2c334 Mon Sep 17 00:00:00 2001 From: Jemn Date: Wed, 7 Aug 2024 00:56:14 -0500 Subject: [PATCH 12/14] ENTREGABLE.md - Descripcion de herramientas y tecnologias utilizadas --- ENTREGABLE.md | 23 +++++++++++++++++++ .../resources/documentacion/api-docs.yaml | 3 ++- .../challenge_prod.table_role.json | 0 .../challenge_prod.table_transaction.json | 0 .../challenge_prod.table_user.json | 0 5 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 ENTREGABLE.md rename src/main/resources/{documento_db => mongo_db}/challenge_prod.table_role.json (100%) rename src/main/resources/{documento_db => mongo_db}/challenge_prod.table_transaction.json (100%) rename src/main/resources/{documento_db => mongo_db}/challenge_prod.table_user.json (100%) diff --git a/ENTREGABLE.md b/ENTREGABLE.md new file mode 100644 index 0000000..d19e20e --- /dev/null +++ b/ENTREGABLE.md @@ -0,0 +1,23 @@ +# API OPERACIONES-MS +Servicio que registra operaciones transactionales manejando eventos en diferentes topicos creados. Correctamente aunthenticado y autorizado para ejecución de cada recurso. + +# Tecnologia y Herramientas + +* Ide Intellij +* Spring Boot para creacion y configuracion de proyecto +* Desarrollo para Webflux +* MongoDB para persistencia reactiva. +* Estructura de Proyecto con modelo MVC +* Kafaka para gestion evento y topicos +* Spring Security para manejo de Authenticacion y Authorizacion +* JWT para manejo de token en validacion de metodos y creacion de usuarios. +* OpenAPi para documentacion y contratos de la api. +* JUnit5 para test de las clases utilizadas. + +# Entregas del Proyecto + +* Repositorio de GitHub con el código fuente +* Git https://github.com/Jemn/java-code-challenge/commits/main/ +* Postman para hacer pruebas de la api. /resource/collection-postman/Interbank.postman_collection.json +* Mongo db Json /resource/mongo-db/challenge_prod.table_role.json - challenge_prod.table_user.json - challenge_prod.table_transaction.json +* Documentacion /resource/documentacion/api-docs.yaml \ No newline at end of file diff --git a/src/main/resources/documentacion/api-docs.yaml b/src/main/resources/documentacion/api-docs.yaml index b6cb378..c852fba 100644 --- a/src/main/resources/documentacion/api-docs.yaml +++ b/src/main/resources/documentacion/api-docs.yaml @@ -1,6 +1,7 @@ openapi: 3.0.1 info: - title: OpenAPI definition + title: API OPERACIONES-MS + description: Servicio que registra operaciones transactionales manejando eventos en diferentes tópicos creados. Correctamente aunthenticado y autorizado para ejecución de cada recurso. version: v0 servers: - url: http://localhost:8082 diff --git a/src/main/resources/documento_db/challenge_prod.table_role.json b/src/main/resources/mongo_db/challenge_prod.table_role.json similarity index 100% rename from src/main/resources/documento_db/challenge_prod.table_role.json rename to src/main/resources/mongo_db/challenge_prod.table_role.json diff --git a/src/main/resources/documento_db/challenge_prod.table_transaction.json b/src/main/resources/mongo_db/challenge_prod.table_transaction.json similarity index 100% rename from src/main/resources/documento_db/challenge_prod.table_transaction.json rename to src/main/resources/mongo_db/challenge_prod.table_transaction.json diff --git a/src/main/resources/documento_db/challenge_prod.table_user.json b/src/main/resources/mongo_db/challenge_prod.table_user.json similarity index 100% rename from src/main/resources/documento_db/challenge_prod.table_user.json rename to src/main/resources/mongo_db/challenge_prod.table_user.json From 9888adb7ea5b10f4d77a8a7c3b87989435063d20 Mon Sep 17 00:00:00 2001 From: Jemn Date: Wed, 7 Aug 2024 13:53:01 -0500 Subject: [PATCH 13/14] version dockerizado --- docker-compose.yml | 34 ++++++++++++++++++++++-------- dockerfile | 12 +++++++++++ src/main/resources/application.yml | 9 +++++--- 3 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index a59bedf..ff727cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,21 @@ version: "3.7" services: - postgres: - image: postgres:14 + mongo: + image: mongo:4.4 ports: - - "5432:5432" - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres + - "27017:27017" + volumes: + - mongo-data:/data/db + zookeeper: image: confluentinc/cp-zookeeper:5.5.3 environment: ZOOKEEPER_CLIENT_PORT: 2181 + kafka: image: confluentinc/cp-enterprise-kafka:5.5.3 - depends_on: [zookeeper] + depends_on: + - zookeeper environment: KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 @@ -23,6 +25,20 @@ services: KAFKA_JMX_PORT: 9991 ports: - 9092:9092 + + app: + image: api-operaciones-ms + build: + context: . + dockerfile: Dockerfile + depends_on: + - mongo + - kafka + environment: + - SPRING_DATA_MONGODB_URI=mongodb://mongo:27017/challenge_prod + - KAFKA_BOOTSTRAP_SERVERS=kafka:29092 + ports: + - "8083:8083" + volumes: - oracle-data: - oracle-backup: + mongo-data: diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..da19fb9 --- /dev/null +++ b/dockerfile @@ -0,0 +1,12 @@ +# Usa una imagen base de Maven para construir el proyecto +FROM maven:3.8.1-openjdk-17 AS build +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn clean package -DskipTests + +# Usa una imagen base de OpenJDK para ejecutar el proyecto +FROM openjdk:17-jdk-slim +WORKDIR /app +COPY --from=build /app/target/challenge-0.0.1-SNAPSHOT.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bb7b15e..1e7b903 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,7 +8,8 @@ spring: name: challenge data: mongodb: - uri: mongodb://localhost:27017/challenge_prod + #uri: mongodb://localhost:27017/challenge_prod + uri: ${SPRING_DATA_MONGODB_URI} security: user: name: admin @@ -16,7 +17,8 @@ spring: #autoconfigure: #exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration kafka: - bootstrap-servers: localhost:9092 + #bootstrap-servers: localhost:9092 + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS} consumer: group-id: myGroup auto-offset-reset: earliest @@ -26,7 +28,8 @@ spring: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer server: - port: 8082 + #port: 8082 + port: 8083 logging: level: From d2dc0ed639db31d33886397bc5a48c8f4d0c33ca Mon Sep 17 00:00:00 2001 From: Jemn Date: Wed, 7 Aug 2024 14:07:23 -0500 Subject: [PATCH 14/14] Coleccion para version dockerizado --- .../Interbank.postman_collection.json | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/main/resources/collection-postman/Interbank.postman_collection.json b/src/main/resources/collection-postman/Interbank.postman_collection.json index 6a9c139..0ebc456 100644 --- a/src/main/resources/collection-postman/Interbank.postman_collection.json +++ b/src/main/resources/collection-postman/Interbank.postman_collection.json @@ -11,7 +11,7 @@ "name": "api-challenge-ms", "item": [ { - "name": "http://localhost:8082/api/login/signin - obtener token", + "name": "2. signin - obtener token", "request": { "method": "POST", "header": [], @@ -25,12 +25,12 @@ } }, "url": { - "raw": "http://localhost:8082/api/login/signin", + "raw": "http://localhost:8083/api/login/signin", "protocol": "http", "host": [ "localhost" ], - "port": "8082", + "port": "8083", "path": [ "api", "login", @@ -41,14 +41,44 @@ "response": [] }, { - "name": "http://localhost:8082/api/transactions/save - rechazado", + "name": "1. signup - crear usuario", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\" : \"prueba\",\r\n \"password\" : \"prueba123\",\r\n \"email\" : \"preuba@hotmail.com\",\r\n \"locked\" : true,\r\n \"disable\" : true,\r\n \"roleDocument\" : {\r\n \"id\" : \"663e6f02c7e1ec15a4d93e67\",\r\n \"role\" : \"ADMIN\"\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8083/api/login/signup", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8083", + "path": [ + "api", + "login", + "signup" + ] + } + }, + "response": [] + }, + { + "name": "3. transactions/save - rechazado", "request": { "auth": { "type": "bearer", "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI", + "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwNTYyNDQsImV4cCI6MTcyMzA1NjU0NH0.sf5w_w8yjmt2YuldFL4PVPrj6FzTYlpy9cdWlZwFfzQ", "type": "string" } ] @@ -65,12 +95,12 @@ } }, "url": { - "raw": "http://localhost:8082/api/transactions/save", + "raw": "http://localhost:8083/api/transactions/save", "protocol": "http", "host": [ "localhost" ], - "port": "8082", + "port": "8083", "path": [ "api", "transactions", @@ -81,14 +111,14 @@ "response": [] }, { - "name": "http://localhost:8082/api/transactions/save - aprobado", + "name": "4. transactions/save - aprobado", "request": { "auth": { "type": "bearer", "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI", + "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwNTYyNDQsImV4cCI6MTcyMzA1NjU0NH0.sf5w_w8yjmt2YuldFL4PVPrj6FzTYlpy9cdWlZwFfzQ", "type": "string" } ] @@ -105,12 +135,12 @@ } }, "url": { - "raw": "http://localhost:8082/api/transactions/save", + "raw": "http://localhost:8083/api/transactions/save", "protocol": "http", "host": [ "localhost" ], - "port": "8082", + "port": "8083", "path": [ "api", "transactions", @@ -121,7 +151,7 @@ "response": [] }, { - "name": "http://localhost:8082/api/transactions/list", + "name": "5. Listar Transactions", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -131,7 +161,7 @@ "bearer": [ { "key": "token", - "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwMDQ0OTEsImV4cCI6MTcyMzAwNDc5MX0.XKTk7TJm1WOtrZSjmDsHR3XmwEIk_Qf8AK4FtlTyXRI", + "value": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcnVlYmEiLCJpYXQiOjE3MjMwNTYyNDQsImV4cCI6MTcyMzA1NjU0NH0.sf5w_w8yjmt2YuldFL4PVPrj6FzTYlpy9cdWlZwFfzQ", "type": "string" } ] @@ -148,17 +178,17 @@ } }, "url": { - "raw": "http://localhost:8082/api/transactions/list/66b2f65a83fe750de3db4986", + "raw": "http://localhost:8083/api/transactions/list/66b3c0a5b806f918e276d782", "protocol": "http", "host": [ "localhost" ], - "port": "8082", + "port": "8083", "path": [ "api", "transactions", "list", - "66b2f65a83fe750de3db4986" + "66b3c0a5b806f918e276d782" ] } },