Spring Boot 3: doc, test y prep. para impl.: Aula 1
This commit is contained in:
parent
dfd811fd81
commit
3b667e35cb
@ -33,12 +33,6 @@ public class SecurityConfigurations {
|
||||
.and()
|
||||
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.build();
|
||||
//return httpSecurity.csrf().disable()
|
||||
// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
// .and().authorizeHttpRequests()
|
||||
// .requestMatchers(HttpMethod.POST, "/login").permitAll()
|
||||
// .anyRequest().authenticated()
|
||||
// .and().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -30,10 +30,10 @@ public class SecurityFilter extends OncePerRequestFilter {
|
||||
if (authHeader != null) {
|
||||
System.out.println("- ".repeat(10) + "filtro no null"+" -".repeat(10));
|
||||
var token = authHeader.replace("Bearer ", "");
|
||||
var subject = tokenService.getSubject(token);
|
||||
if (subject != null) {
|
||||
var nombreUsuario = tokenService.getSubject(token);
|
||||
if (nombreUsuario != null) {
|
||||
// token válido
|
||||
var usuario = usuarioRepository.findByLogin(subject);
|
||||
var usuario = usuarioRepository.findByLogin(nombreUsuario);
|
||||
var authentication = new UsernamePasswordAuthenticationToken(
|
||||
usuario,
|
||||
null,
|
||||
|
308
010_spring_boot/api_rest/api3/mvnw
vendored
Executable file
308
010_spring_boot/api_rest/api3/mvnw
vendored
Executable file
@ -0,0 +1,308 @@
|
||||
#!/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.2.0
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||
. /usr/local/etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "$(uname)" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
|
||||
else
|
||||
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=$(java-config --jre-home)
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="$(which javac)"
|
||||
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=$(which readlink)
|
||||
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
|
||||
else
|
||||
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
|
||||
fi
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=$(cd "$wdir/.." || exit 1; pwd)
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
# Remove \r in case we run on Windows within Git Bash
|
||||
# and check out the repository with auto CRLF management
|
||||
# enabled. Otherwise, we may read lines that are delimited with
|
||||
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
|
||||
# splitting rules.
|
||||
tr -s '\r\n' ' ' < "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
log() {
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
printf '%s\n' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
|
||||
log "$MAVEN_PROJECTBASEDIR"
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if [ -r "$wrapperJarPath" ]; then
|
||||
log "Found $wrapperJarPath"
|
||||
else
|
||||
log "Couldn't find $wrapperJarPath, downloading it ..."
|
||||
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
else
|
||||
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
fi
|
||||
while IFS="=" read -r key value; do
|
||||
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
|
||||
safeValue=$(echo "$value" | tr -d '\r')
|
||||
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
|
||||
esac
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
log "Downloading from: $wrapperUrl"
|
||||
|
||||
if $cygwin; then
|
||||
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
log "Found wget ... using wget"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
else
|
||||
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
log "Found curl ... using curl"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
else
|
||||
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
else
|
||||
log "Falling back to using Java to download"
|
||||
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaSource=$(cygpath --path --windows "$javaSource")
|
||||
javaClass=$(cygpath --path --windows "$javaClass")
|
||||
fi
|
||||
if [ -e "$javaSource" ]; then
|
||||
if [ ! -e "$javaClass" ]; then
|
||||
log " - Compiling MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/javac" "$javaSource")
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
log " - Running MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
wrapperSha256Sum=""
|
||||
while IFS="=" read -r key value; do
|
||||
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
|
||||
esac
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ -n "$wrapperSha256Sum" ]; then
|
||||
wrapperSha256Result=false
|
||||
if command -v sha256sum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
elif command -v shasum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
|
||||
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
|
||||
exit 1
|
||||
fi
|
||||
if [ $wrapperSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
|
||||
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
# shellcheck disable=SC2086 # safe args
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
$MAVEN_DEBUG_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
205
010_spring_boot/api_rest/api3/mvnw.cmd
vendored
Normal file
205
010_spring_boot/api_rest/api3/mvnw.cmd
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
@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.2.0
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %WRAPPER_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
SET WRAPPER_SHA_256_SUM=""
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
|
||||
)
|
||||
IF NOT %WRAPPER_SHA_256_SUM%=="" (
|
||||
powershell -Command "&{"^
|
||||
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
|
||||
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
|
||||
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
|
||||
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
|
||||
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
|
||||
" exit 1;"^
|
||||
"}"^
|
||||
"}"
|
||||
if ERRORLEVEL 1 goto error
|
||||
)
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% ^
|
||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||
%MAVEN_OPTS% ^
|
||||
%MAVEN_DEBUG_OPTS% ^
|
||||
-classpath %WRAPPER_JAR% ^
|
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||
|
||||
cmd /C exit /B %ERROR_CODE%
|
99
010_spring_boot/api_rest/api3/pom.xml
Normal file
99
010_spring_boot/api_rest/api3/pom.xml
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.1.3</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>med.voll</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>api</name>
|
||||
<description>API Rest para clínica Voll</description>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>4.4.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,13 @@
|
||||
package med.voll.api;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class ApiApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ApiApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package med.voll.api.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.domain.usuario.DatosAutenticacionUsuario;
|
||||
import med.voll.api.domain.usuario.Usuario;
|
||||
import med.voll.api.infra.security.DatosJWTtoken;
|
||||
import med.voll.api.infra.security.TokenService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/login")
|
||||
public class AutenticacionController {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity autenticarUsuario(@RequestBody @Valid DatosAutenticacionUsuario datosAutenticacionUsuario) {
|
||||
Authentication authtoken = new UsernamePasswordAuthenticationToken(
|
||||
datosAutenticacionUsuario.login(),
|
||||
datosAutenticacionUsuario.clave());
|
||||
var usuarioAutenticado = authenticationManager.authenticate(authtoken);
|
||||
var JWTtoken = tokenService.generarToken((Usuario) usuarioAutenticado.getPrincipal());
|
||||
return ResponseEntity.ok(new DatosJWTtoken(JWTtoken));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package med.voll.api.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.domain.consulta.AgendaDeConsultaService;
|
||||
import med.voll.api.domain.consulta.DatosAgendarConsulta;
|
||||
import med.voll.api.domain.consulta.DatosDetalleConsulta;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@ResponseBody
|
||||
@RequestMapping("/consultas")
|
||||
public class ConsultaController {
|
||||
|
||||
@Autowired
|
||||
private AgendaDeConsultaService service;
|
||||
|
||||
@PostMapping
|
||||
@Transactional // ojo de donde se importa esta anotación
|
||||
public ResponseEntity agendar(@RequestBody @Valid DatosAgendarConsulta datos) {
|
||||
System.out.println(datos);
|
||||
service.agendar(datos);
|
||||
|
||||
return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package med.voll.api.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/hello")
|
||||
public class HelloController {
|
||||
|
||||
@GetMapping
|
||||
public String helloWorld() {
|
||||
return "Hello World! Test AutoReload";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package med.voll.api.controller;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.medico.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/medicos")
|
||||
public class MedicoController {
|
||||
|
||||
@Autowired
|
||||
private MedicoRepository medicoRepository;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<DatosRespuestaMedico> registrarMedico(
|
||||
@RequestBody @Valid DatosRegistroMedico datosRegistroMedico,
|
||||
UriComponentsBuilder uriComponentsBuilder) {
|
||||
Medico medico = medicoRepository.save(new Medico(datosRegistroMedico));
|
||||
DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
|
||||
medico, new DatosDireccion(medico.getDireccion())
|
||||
);
|
||||
URI url = uriComponentsBuilder.path("/medicos/{id}") .buildAndExpand(medico.getId()).toUri();
|
||||
return ResponseEntity.created(url).body(datosRespuestaMedico);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<DatosListadoMedicos>> listadoMedicos(
|
||||
@PageableDefault(size = 5) Pageable paginacion) {
|
||||
return ResponseEntity.ok(medicoRepository.findByActivoTrue(paginacion).map(DatosListadoMedicos::new));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Transactional
|
||||
public ResponseEntity<DatosRespuestaMedico> actualizarMedico(
|
||||
@RequestBody @Valid DatosActualizarMedico datosActualizarMedico) {
|
||||
Medico medico = medicoRepository.getReferenceById(datosActualizarMedico.id());
|
||||
medico.actualizarDatos(datosActualizarMedico);
|
||||
DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
|
||||
medico, new DatosDireccion(medico.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosRespuestaMedico);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Transactional
|
||||
public ResponseEntity eliminarMedico(@PathVariable Long id) {
|
||||
Medico medico = medicoRepository.getReferenceById(id);
|
||||
medico.desactivarMedico();
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<DatosRespuestaMedico> retornaDatosMedico(@PathVariable Long id) {
|
||||
Medico medico = medicoRepository.getReferenceById(id);
|
||||
DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
|
||||
medico, new DatosDireccion(medico.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosRespuestaMedico);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package med.voll.api.controller;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
import med.voll.api.domain.paciente.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/pacientes")
|
||||
public class PacienteController {
|
||||
|
||||
/* No es recomendable usar @Autowired a nivel de declaración pues genera
|
||||
problemas al realizar pruebas unitarias */
|
||||
@Autowired
|
||||
private PacienteRepository pacienteRepository;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<DatosRespuestaPaciente> registrarPaciente(
|
||||
@RequestBody @Valid DatosRegistroPaciente datosRegistroPaciente,
|
||||
UriComponentsBuilder uriComponentsBuilder) {
|
||||
Paciente paciente = pacienteRepository.save(new Paciente(datosRegistroPaciente));
|
||||
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
|
||||
paciente, new DatosDireccion(paciente.getDireccion())
|
||||
);
|
||||
URI url = uriComponentsBuilder.path("/pacientes/{id}") .buildAndExpand(paciente.getId()).toUri();
|
||||
return ResponseEntity.created(url).body(datosRespuestaPaciente);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<DatosListadoPacientes>> listadoPacientes(
|
||||
@PageableDefault(size = 5) Pageable paginacion) {
|
||||
return ResponseEntity.ok(
|
||||
pacienteRepository.findByActivoTrue(paginacion).map(DatosListadoPacientes::new)
|
||||
);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@Transactional
|
||||
public ResponseEntity<DatosRespuestaPaciente> actualizarPaciente(
|
||||
@RequestBody @Valid DatosActualizarPaciente datosActualizarPaciente) {
|
||||
Paciente paciente = pacienteRepository.getReferenceById(datosActualizarPaciente.id());
|
||||
paciente.actualizarDatos(datosActualizarPaciente);
|
||||
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
|
||||
paciente, new DatosDireccion(paciente.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosRespuestaPaciente);
|
||||
}
|
||||
|
||||
// Desactivar Paciente
|
||||
@DeleteMapping("/{id}")
|
||||
@Transactional
|
||||
public ResponseEntity eliminarPaciente(@PathVariable Long id) {
|
||||
Paciente paciente = pacienteRepository.getReferenceById(id);
|
||||
paciente.desactivarPaciente();
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<DatosRespuestaPaciente> retornaDatosPaciente(@PathVariable Long id) {
|
||||
Paciente paciente = pacienteRepository.getReferenceById(id);
|
||||
DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
|
||||
paciente, new DatosDireccion(paciente.getDireccion())
|
||||
);
|
||||
return ResponseEntity.ok(datosRespuestaPaciente);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package med.voll.api.domain.consulta;
|
||||
|
||||
import med.voll.api.domain.medico.Medico;
|
||||
import med.voll.api.domain.medico.MedicoRepository;
|
||||
import med.voll.api.domain.paciente.Paciente;
|
||||
import med.voll.api.domain.paciente.PacienteRepository;
|
||||
import med.voll.api.infra.errores.ValidacionDeIntegridad;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AgendaDeConsultaService {
|
||||
|
||||
@Autowired
|
||||
private ConsultaRepository consultaRepository;
|
||||
@Autowired
|
||||
private MedicoRepository medicoRepository;
|
||||
@Autowired
|
||||
private PacienteRepository pacienteRepository;
|
||||
|
||||
public void agendar(DatosAgendarConsulta datos) {
|
||||
|
||||
if (!pacienteRepository.findById(datos.idPaciente()).isPresent()) {
|
||||
throw new ValidacionDeIntegridad("Id de paciente no encontrado");
|
||||
}
|
||||
if (datos.idMedico() != null && !medicoRepository.existsById(datos.idMedico())) {
|
||||
throw new ValidacionDeIntegridad("Id de médico no encontrado");
|
||||
}
|
||||
|
||||
var paciente = pacienteRepository.findById(datos.idPaciente()).get();
|
||||
var medico = seleccionarMedico(datos);
|
||||
var consulta = new Consulta(null, medico, paciente, datos.fecha());
|
||||
consultaRepository.save(consulta);
|
||||
}
|
||||
|
||||
private Medico seleccionarMedico(DatosAgendarConsulta datos) {
|
||||
if (datos.idMedico() != null) {
|
||||
return medicoRepository.getReferenceById(datos.idMedico());
|
||||
}
|
||||
if (datos.especialidad() == null) {
|
||||
throw new ValidacionDeIntegridad("Debe indicarse una especialidad médica");
|
||||
}
|
||||
return medicoRepository.seleccionarMedicoConEspecialidadEnFecha(datos.especialidad(), datos.fecha());
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package med.voll.api.domain.consulta;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import med.voll.api.domain.medico.Medico;
|
||||
import med.voll.api.domain.paciente.Paciente;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Table(name = "consultas")
|
||||
@Entity(name = "Consulta")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Consulta {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "medico_id")
|
||||
private Medico medico;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paciente_id")
|
||||
private Paciente paciente;
|
||||
|
||||
private LocalDateTime fecha;
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package med.voll.api.domain.consulta;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ConsultaRepository extends JpaRepository<Consulta, Long> {
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package med.voll.api.domain.consulta;
|
||||
|
||||
import jakarta.validation.constraints.Future;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.domain.medico.Especialidad;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record DatosAgendarConsulta(
|
||||
@NotNull Long idPaciente,
|
||||
Long idMedico,
|
||||
@NotNull @Future LocalDateTime fecha,
|
||||
Especialidad especialidad) {
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package med.voll.api.domain.consulta;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record DatosDetalleConsulta(Long id, Long idPaciente, Long idMedico, LocalDateTime fecha) {
|
||||
public DatosDetalleConsulta(Consulta consulta) {
|
||||
this(consulta.getId(),
|
||||
consulta.getPaciente().getId(),
|
||||
consulta.getMedico().getId(),
|
||||
consulta.getFecha());
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package med.voll.api.domain.direccion;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record DatosDireccion(
|
||||
@NotBlank String calle,
|
||||
@NotBlank String distrito,
|
||||
@NotBlank String ciudad,
|
||||
String numero,
|
||||
String complemento) {
|
||||
|
||||
public DatosDireccion(Direccion direccion){
|
||||
this(direccion.getCalle(),
|
||||
direccion.getDistrito(),
|
||||
direccion.getCiudad(),
|
||||
direccion.getNumero(),
|
||||
direccion.getComplemento()
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package med.voll.api.domain.direccion;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Direccion {
|
||||
|
||||
private String calle;
|
||||
private String numero;
|
||||
private String complemento;
|
||||
private String distrito;
|
||||
private String ciudad;
|
||||
|
||||
public Direccion(DatosDireccion direccion) {
|
||||
this.calle = direccion.calle();
|
||||
this.numero = direccion.numero();
|
||||
this.complemento = direccion.complemento();
|
||||
this.distrito = direccion.distrito();
|
||||
this.ciudad = direccion.ciudad();
|
||||
}
|
||||
|
||||
public Direccion actualizarDatosDireccion(DatosDireccion direccion) {
|
||||
this.calle = direccion.calle();
|
||||
this.numero = direccion.numero();
|
||||
this.complemento = direccion.complemento();
|
||||
this.distrito = direccion.distrito();
|
||||
this.ciudad = direccion.ciudad();
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosActualizarMedico(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) {
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
public record DatosListadoMedicos(
|
||||
Long id,
|
||||
String nombre,
|
||||
String especialidad,
|
||||
String documento, String email) {
|
||||
|
||||
public DatosListadoMedicos (Medico medico) {
|
||||
this(medico.getId(),
|
||||
medico.getNombre(),
|
||||
medico.getEspecialidad().toString(),
|
||||
medico.getDocumento(),
|
||||
medico.getEmail());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRegistroMedico(
|
||||
@NotBlank String nombre,
|
||||
@NotBlank @Email String email,
|
||||
@NotBlank String telefono,
|
||||
@NotBlank @Pattern(regexp = "\\d{4,6}") String documento,
|
||||
@NotNull Especialidad especialidad,
|
||||
@NotNull @Valid DatosDireccion direccion
|
||||
) {}
|
@ -0,0 +1,19 @@
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRespuestaMedico(@NotNull Long id, String nombre,
|
||||
String email, String telefono, String documento,
|
||||
DatosDireccion direccion) {
|
||||
|
||||
public DatosRespuestaMedico(Medico medico, DatosDireccion direccion){
|
||||
this(medico.getId(),
|
||||
medico.getNombre(),
|
||||
medico.getEmail(),
|
||||
medico.getTelefono(),
|
||||
medico.getDocumento(),
|
||||
direccion);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
public enum Especialidad {
|
||||
ORTOPEDIA,
|
||||
CARDIOLOGIA,
|
||||
GINECOLOGIA,
|
||||
PEDIATRIA
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import med.voll.api.domain.direccion.Direccion;
|
||||
|
||||
@Table(name="medicos")
|
||||
@Entity(name="Medico")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Medico {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String nombre;
|
||||
private String email;
|
||||
private String telefono;
|
||||
private String documento;
|
||||
private Boolean activo;
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Especialidad especialidad;
|
||||
@Embedded
|
||||
private Direccion direccion;
|
||||
|
||||
public Medico(DatosRegistroMedico datosRegistroMedico) {
|
||||
this.activo = true;
|
||||
this.nombre = datosRegistroMedico.nombre();
|
||||
this.email = datosRegistroMedico.email();
|
||||
this.documento = datosRegistroMedico.documento();
|
||||
this.telefono = datosRegistroMedico.telefono();
|
||||
this.especialidad = datosRegistroMedico.especialidad();
|
||||
this.direccion = new Direccion(datosRegistroMedico.direccion());
|
||||
}
|
||||
|
||||
public void actualizarDatos(DatosActualizarMedico datosActualizarMedico) {
|
||||
if (datosActualizarMedico.nombre() != null) {
|
||||
this.nombre = datosActualizarMedico.nombre();
|
||||
}
|
||||
if (datosActualizarMedico.documento() != null) {
|
||||
this.documento = datosActualizarMedico.documento();
|
||||
}
|
||||
if (datosActualizarMedico.direccion() != null) {
|
||||
this.direccion = direccion.actualizarDatosDireccion(datosActualizarMedico.direccion());
|
||||
}
|
||||
}
|
||||
|
||||
public void desactivarMedico() {
|
||||
this.activo = false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package med.voll.api.domain.medico;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public interface MedicoRepository extends JpaRepository<Medico, Long> {
|
||||
Page<Medico> findByActivoTrue(Pageable paginacion);
|
||||
|
||||
//@Query("select Medico from Medico m where m.activo=true and m.especialidad=:especialidad")
|
||||
//@Query("""
|
||||
// SELECT m FROM Medico m
|
||||
// WHERE m.activo=1 and
|
||||
// m.especialidad=:especialidad and
|
||||
// m.id NOT IN(
|
||||
// SELECT c.medico.id FROM Consulta c
|
||||
// WHERE c.fecha=:fecha
|
||||
// ) ORDER BY RAND() LIMIT 1
|
||||
// """)
|
||||
//@Query("""
|
||||
// SELECT m FROM Medico m
|
||||
// WHERE m.activo=true
|
||||
// AND m.especialidad=?1 AND m.id NOT IN(
|
||||
// SELECT c.medico.id FROM Consulta c
|
||||
// WHERE c.fecha=?2
|
||||
// ) ORDER BY RAND() LIMIT 1
|
||||
// """)
|
||||
@Query("""
|
||||
select m from Medico m
|
||||
where m.activo= true\s
|
||||
and
|
||||
m.especialidad=:especialidad\s
|
||||
and
|
||||
m.id not in( \s
|
||||
select c.medico.id from Consulta c
|
||||
where
|
||||
c.fecha=:fecha
|
||||
)
|
||||
order by rand()
|
||||
limit 1
|
||||
""")
|
||||
Medico seleccionarMedicoConEspecialidadEnFecha(Especialidad especialidad, LocalDateTime fecha);
|
||||
|
||||
//@Query("""
|
||||
// select m.activo
|
||||
// from Medico m
|
||||
// where m.id=:idMedico
|
||||
// """)
|
||||
//Boolean findActivoById(Long idMedico);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosActualizarPaciente(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) {
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
public record DatosListadoPacientes(Long id, String nombre, String documento, String email) {
|
||||
|
||||
public DatosListadoPacientes(Paciente paciente) {
|
||||
this(paciente.getId(),
|
||||
paciente.getNombre(),
|
||||
paciente.getDocumento(),
|
||||
paciente.getEmail());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRegistroPaciente(
|
||||
@NotBlank String nombre,
|
||||
@NotBlank @Email String email,
|
||||
@NotBlank String telefono,
|
||||
@NotBlank @Pattern(regexp = "\\d{4,6}") String documento,
|
||||
@NotNull @Valid DatosDireccion direccion
|
||||
) {}
|
@ -0,0 +1,19 @@
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import med.voll.api.domain.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRespuestaPaciente(@NotNull Long id, String nombre,
|
||||
String email, String telefono, String documento,
|
||||
DatosDireccion direccion) {
|
||||
|
||||
public DatosRespuestaPaciente(Paciente paciente, DatosDireccion direccion){
|
||||
this(paciente.getId(),
|
||||
paciente.getNombre(),
|
||||
paciente.getEmail(),
|
||||
paciente.getTelefono(),
|
||||
paciente.getDocumento(),
|
||||
direccion);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import med.voll.api.domain.direccion.Direccion;
|
||||
|
||||
@Table(name="pacientes")
|
||||
@Entity(name="Paciente")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Paciente {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String nombre;
|
||||
private String email;
|
||||
private String telefono;
|
||||
private String documento;
|
||||
private Boolean activo;
|
||||
@Embedded
|
||||
private Direccion direccion;
|
||||
|
||||
public Paciente(DatosRegistroPaciente datosRegistroPaciente) {
|
||||
this.activo = true;
|
||||
this.nombre = datosRegistroPaciente.nombre();
|
||||
this.email = datosRegistroPaciente.email();
|
||||
this.documento = datosRegistroPaciente.documento();
|
||||
this.telefono = datosRegistroPaciente.telefono();
|
||||
this.direccion = new Direccion(datosRegistroPaciente.direccion());
|
||||
}
|
||||
|
||||
public void actualizarDatos(DatosActualizarPaciente datosActualizarPaciente) {
|
||||
if (datosActualizarPaciente.nombre() != null) {
|
||||
this.nombre = datosActualizarPaciente.nombre();
|
||||
}
|
||||
if (datosActualizarPaciente.documento() != null) {
|
||||
this.documento = datosActualizarPaciente.documento();
|
||||
}
|
||||
if (datosActualizarPaciente.direccion() != null) {
|
||||
this.direccion = direccion.actualizarDatosDireccion(datosActualizarPaciente.direccion());
|
||||
}
|
||||
}
|
||||
|
||||
public void desactivarPaciente() {
|
||||
this.activo = false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package med.voll.api.domain.paciente;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface PacienteRepository extends JpaRepository<Paciente, Long> {
|
||||
Page<Paciente> findByActivoTrue(Pageable paginacion);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package med.voll.api.domain.usuario;
|
||||
|
||||
public record DatosAutenticacionUsuario(String login, String clave) {
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package med.voll.api.domain.usuario;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Table(name = "usuarios")
|
||||
@Entity(name = "Usuario")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Usuario implements UserDetails {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String login;
|
||||
private String clave;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return clave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return login;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package med.voll.api.domain.usuario;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
|
||||
UserDetails findByLogin(String login);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package med.voll.api.infra.errores;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ManejadorDeErrores {
|
||||
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ResponseEntity manejarError404(){
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity manejarError400(MethodArgumentNotValidException e){
|
||||
var errores = e.getFieldErrors().stream().map(DatosErrorValidacion::new).toList();
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity manejarError500(DataIntegrityViolationException e) {
|
||||
var errores = e.getMostSpecificCause().getLocalizedMessage();
|
||||
return ResponseEntity.badRequest().body(errores);
|
||||
}
|
||||
|
||||
private record DatosErrorValidacion(String campo, String error) {
|
||||
public DatosErrorValidacion(FieldError error) {
|
||||
this(error.getField(), error.getDefaultMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package med.voll.api.infra.errores;
|
||||
|
||||
public class ValidacionDeIntegridad extends RuntimeException {
|
||||
public ValidacionDeIntegridad(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package med.voll.api.infra.security;
|
||||
|
||||
import med.voll.api.domain.usuario.UsuarioRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AutenticacionService implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private UsuarioRepository usuarioRepository;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
|
||||
return usuarioRepository.findByLogin(login);
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package med.voll.api.infra.security;
|
||||
|
||||
public record DatosJWTtoken(String jwTtoken) {
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package med.voll.api.infra.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfigurations {
|
||||
|
||||
@Autowired
|
||||
private SecurityFilter securityFilter;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
|
||||
return httpSecurity.csrf().disable().sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and().authorizeHttpRequests()
|
||||
.requestMatchers(HttpMethod.POST, "/login")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated()
|
||||
.and()
|
||||
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(
|
||||
AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
||||
return authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder () {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package med.voll.api.infra.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import med.voll.api.domain.usuario.UsuarioRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class SecurityFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
@Autowired
|
||||
private UsuarioRepository usuarioRepository;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain
|
||||
) throws ServletException, IOException {
|
||||
var authHeader = request.getHeader("Authorization");
|
||||
if (authHeader != null) {
|
||||
var token = authHeader.replace("Bearer ", "");
|
||||
var nombreUsuario = tokenService.getSubject(token);
|
||||
if (nombreUsuario != null) {
|
||||
// token válido
|
||||
var usuario = usuarioRepository.findByLogin(nombreUsuario);
|
||||
var authentication = new UsernamePasswordAuthenticationToken(
|
||||
usuario,
|
||||
null,
|
||||
usuario.getAuthorities() // forzar inicio de sesión
|
||||
);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package med.voll.api.infra.security;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.JWTCreationException;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import med.voll.api.domain.usuario.Usuario;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
@Service
|
||||
public class TokenService {
|
||||
|
||||
@Value("${api.security.secret}")
|
||||
private String apiSecret;
|
||||
|
||||
public String generarToken(Usuario usuario) {
|
||||
try {
|
||||
Algorithm algorithm = Algorithm.HMAC256(apiSecret) ;
|
||||
return JWT.create()
|
||||
.withIssuer("voll med")
|
||||
.withSubject(usuario.getLogin())
|
||||
.withClaim("id", usuario.getId())
|
||||
.withExpiresAt(generarFechaExpiracion())
|
||||
.sign(algorithm);
|
||||
} catch (JWTCreationException exception){
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
public String getSubject(String token) {
|
||||
if (token == null) {
|
||||
throw new RuntimeException("token nulo");
|
||||
}
|
||||
DecodedJWT verifier = null;
|
||||
try {
|
||||
Algorithm algorithm = Algorithm.HMAC256(apiSecret);
|
||||
verifier = JWT.require(algorithm)
|
||||
// specify an specific claim validations
|
||||
.withIssuer("voll med")
|
||||
// reusable verifier instance
|
||||
.build()
|
||||
.verify(token);
|
||||
verifier.getSubject();
|
||||
} catch (JWTVerificationException exception) {
|
||||
// Invalid signature/claims
|
||||
System.out.println(exception.toString());
|
||||
}
|
||||
if (verifier.getSubject() == null) {
|
||||
throw new RuntimeException("Verifier inválido");
|
||||
}
|
||||
return verifier.getSubject();
|
||||
}
|
||||
|
||||
private Instant generarFechaExpiracion() {
|
||||
return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
spring.datasource.url=jdbc:mysql://${DB_URL}
|
||||
spring.datasource.username=${DB_USER}
|
||||
spring.datasource.password=${DB_PASS}
|
||||
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.format_sql=true
|
||||
|
||||
server.error.include-stacktrace=never
|
||||
|
||||
api.security.secret=${JWT_SECRET}
|
||||
|
||||
spring.main.banner-mode=CONSOLE
|
||||
spring.output.ansi.enabled=ALWAYS
|
@ -0,0 +1,17 @@
|
||||
create table medicos(
|
||||
|
||||
id bigint not null auto_increment,
|
||||
nombre varchar(100) not null,
|
||||
email varchar(100) not null unique,
|
||||
documento varchar(6) not null unique,
|
||||
especialidad varchar(100) not null,
|
||||
telefono varchar(20) not null,
|
||||
calle varchar(100) not null,
|
||||
distrito varchar(100) not null,
|
||||
complemento varchar(100),
|
||||
numero varchar(20),
|
||||
ciudad varchar(100) not null,
|
||||
|
||||
primary key(id)
|
||||
|
||||
);
|
@ -0,0 +1,16 @@
|
||||
create table pacientes(
|
||||
|
||||
id bigint not null auto_increment,
|
||||
nombre varchar(100) not null,
|
||||
email varchar(100) not null unique,
|
||||
documento varchar(6) not null unique,
|
||||
telefono varchar(20) not null,
|
||||
calle varchar(100) not null,
|
||||
distrito varchar(100) not null,
|
||||
complemento varchar(100),
|
||||
numero varchar(20),
|
||||
ciudad varchar(100) not null,
|
||||
|
||||
primary key(id)
|
||||
|
||||
);
|
@ -0,0 +1,2 @@
|
||||
alter table medicos add activo tinyint;
|
||||
update medicos set activo=1;
|
@ -0,0 +1,2 @@
|
||||
alter table pacientes add activo tinyint;
|
||||
update pacientes set activo=1;
|
@ -0,0 +1,9 @@
|
||||
create table usuarios(
|
||||
|
||||
id bigint not null auto_increment,
|
||||
login varchar(100) not null unique,
|
||||
clave varchar(300) not null,
|
||||
|
||||
primary key(id)
|
||||
|
||||
);
|
@ -0,0 +1,12 @@
|
||||
CREATE TABLE consultas(
|
||||
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
medico_id BIGINT NOT NULL,
|
||||
paciente_id BIGINT NOT NULL,
|
||||
fecha DATETIME NOT NULL,
|
||||
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT fk_consultas_medico_id FOREIGN KEY(medico_id) REFERENCES medicos(id),
|
||||
CONSTRAINT fk_consultas_paciente_id FOREIGN KEY(paciente_id) REFERENCES pacientes(id)
|
||||
|
||||
);
|
@ -0,0 +1,13 @@
|
||||
package med.voll.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class ApiApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
@ -951,3 +951,7 @@ Más detalles sobre la función de seguridad del método en la
|
||||
documentación de
|
||||
[Spring Security](https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html)
|
||||
|
||||
---
|
||||
|
||||
Continua en
|
||||
[Documentar, probar y preparar una API para su implementación](./spring_boot_3.md)
|
||||
|
@ -0,0 +1,614 @@
|
||||
# API Rest Java - Documentar, Probar y Preparar una API para su Implementación
|
||||
|
||||
Continuación de
|
||||
[Buenas Prácticas y Protección de una API Rest](./spring_boot_2.md) donde se
|
||||
vio:
|
||||
|
||||
- Creación de un API Rest
|
||||
- Crud (Create, Read, Update, Delete)
|
||||
- Validaciones
|
||||
- Paginación y orden
|
||||
- Buenas prácticas REST
|
||||
- Tratamiento de errores
|
||||
- Control de acceso con JWT
|
||||
|
||||
### Objetivos
|
||||
|
||||
- Funcionalidad agendar consultas
|
||||
- Documentación de la API
|
||||
- Tests Automatizados
|
||||
- Build del proyecto
|
||||
|
||||
[trello](https://trello.com/b/NiWWC55L/vollmed-api-3ra-parte) -
|
||||
[figma](https://www.figma.com/file/tWpylp7pB4n8rX1AsKTsY2/Voll_med-FRONT-Mobile)
|
||||
|
||||
|
||||
## Nuevas Funcionalidades
|
||||
|
||||
- Controller
|
||||
- DTOs
|
||||
- Entidades JPA
|
||||
- Repository
|
||||
- Migration
|
||||
- Security
|
||||
- Reglas de negocio
|
||||
|
||||
Proyecto [Voll_Med API](./api_rest/api3/)
|
||||
|
||||
Para implementar esta u otras funcionalidades, siempre es necesario crear los
|
||||
siguientes tipos de códigos:
|
||||
|
||||
- **Controller**(s), para mapear la solicitud de la nueva funcionalidad
|
||||
- **DTO**s, que representan los datos que llegan y salen de la API
|
||||
- **Entidad**(es) **JPA**
|
||||
- **Repository**(s), para aislar el acceso a la base de datos
|
||||
- **Migration**, para hacer las alteraciones en la base de datos
|
||||
|
||||
Estos son los cinco tipos de código que **siempre** se desarrollan para una nueva
|
||||
funcionalidad. Esto también se aplica al agendamiento de las consultas,
|
||||
incluyendo un sexto elemento a la lista, ***las reglas de negocio***.
|
||||
En este proyecto, se implementan las reglas de negocio con algoritmos más
|
||||
complejos.
|
||||
|
||||
### Implementando la funcionalidad
|
||||
|
||||
Se desarrolla la funcionalidad por partes. Empezaremos por los primeros cinco
|
||||
elementos de la lista, que son más básicos. Luego, la parte de reglas de negocio.
|
||||
|
||||
Se crea un nuevo `ConsultaController` en el paquete
|
||||
[`src.main.java.med.voll.api.controller`](./api_rest/api2/src/main/java/med/voll/api/controller).
|
||||
|
||||
La idea es tener un **Controller** para recibir esas solicitudes relacionadas con
|
||||
el agendado de consultas.
|
||||
|
||||
Es una clase Controller, con las ***anotaciones*** de Spring `@Controller`,
|
||||
`@ResponseBody`, `@RequestMapping("consultas")` o `@RestController`. Mapea las
|
||||
solicitudes que llegan con la **URI** `/consultas`, que debe llamar a este
|
||||
controller y no a los otros.
|
||||
|
||||
Luego, el método anotado con `@PostMapping`. Entonces, la solicitud para programar
|
||||
una consulta será del tipo **POST**, como en otras funcionalidades.
|
||||
|
||||
[ConsultaController](./api_rest/api3/src/main/java/med/voll/api/controller/ConsultaController.java)
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@ResponseBody
|
||||
@RequestMapping("/consultas")
|
||||
public class ConsultaController {
|
||||
|
||||
@Autowired
|
||||
private AgendaDeConsultaService service;
|
||||
|
||||
@PostMapping
|
||||
@Transactional
|
||||
public ResponseEntity agendar(
|
||||
@RequestBody @Valid DatosAgendarConsulta datos) {
|
||||
System.out.println(datos);
|
||||
service.agendar(datos);
|
||||
return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
|
||||
se trata de un registro similar a los que visto anteriormente. Tiene los campos
|
||||
que provienen de la API (`Long idMedico`, `Long idPaciente` y
|
||||
`LocalDateTime fecha`) y las anotaciones de **BEAN validation** `@NotNull` para el
|
||||
`Id` del paciente y para la `fecha`, además de que la fecha debe ser en el
|
||||
**futuro** (`@Future`), es decir, no se podrá programar una consulta en días
|
||||
pasados.
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@ResponseBody
|
||||
@RequestMapping("/consultas")
|
||||
public class ConsultaController {
|
||||
|
||||
@Autowired
|
||||
private AgendaDeConsultaService service;
|
||||
|
||||
@PostMapping
|
||||
@Transactional
|
||||
public ResponseEntity agendar(@RequestBody @Valid DatosAgendarConsulta datos) {
|
||||
System.out.println(datos);
|
||||
service.agendar(datos);
|
||||
return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Al volver al Controller, el otro DTO es el de respuesta, llamado
|
||||
`DatosDetalleConsulta`. Devuelve el `ID` de la consulta creada, del **médico**,
|
||||
del **paciente** y la **fecha de la consulta** registrada en el sistema.
|
||||
|
||||
En el paquete `src.main.java.med.voll.api.domain`, se crea el subpaquete
|
||||
`consulta`, que abarca las clases relacionadas con el dominio de consulta.
|
||||
|
||||
Entre ellas, la ***entidad JPA***
|
||||
[Consulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java),
|
||||
que contiene las anotaciones de ***JPA*** y ***Lombok***, así como la
|
||||
información de la consulta: `medico`, `paciente` y `fecha`.
|
||||
|
||||
```java
|
||||
@Table(name = "consultas")
|
||||
@Entity(name = "Consulta")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Consulta {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "medico_id")
|
||||
private Medico medico;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "paciente_id")
|
||||
private Paciente paciente;
|
||||
|
||||
private LocalDateTime fecha;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
En este caso, `medico` y `paciente` son relaciones con las otras entidades
|
||||
`Medico` y `Paciente`.
|
||||
|
||||
También se crea
|
||||
[ConsultaRepository](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java),
|
||||
que está vacío por el momento.
|
||||
|
||||
```java
|
||||
@Repository
|
||||
public interface ConsultaRepository extends JpaRepository<Consulta, Long> {}
|
||||
```
|
||||
|
||||
Por último, la migración número 6 (`V6`) en
|
||||
`src.main.java.med.voll.api.resources.db.migration`, que crea la tabla de
|
||||
consultas.
|
||||
|
||||
```sql
|
||||
CREATE TABLE consultas(
|
||||
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
medico_id BIGINT NOT NULL,
|
||||
paciente_id BIGINT NOT NULL,
|
||||
fecha DATETIME NOT NULL,
|
||||
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT fk_consultas_medico_id FOREIGN KEY(medico_id)
|
||||
REFERENCES medicos(id),
|
||||
CONSTRAINT fk_consultas_paciente_id FOREIGN KEY(paciente_id)
|
||||
REFERENCES pacientes(id)
|
||||
|
||||
);
|
||||
```
|
||||
|
||||
Los campos de `Id` de **consulta**, `Id` de **paciente**, `Id` de **médico** y
|
||||
`fecha`, donde `medico_id` y `paciente_id` son ***claves foráneas*** que apuntan
|
||||
a las tablas `medicos` y `pacientes`.
|
||||
|
||||
Estos son los códigos estándar para cualquier funcionalidad, con sus respectivos
|
||||
cambios de acuerdo con el proyecto. Cada uno creará un controlador o una entidad
|
||||
distinta, pero el funcionamiento es el mismo.
|
||||
|
||||
Ahora se puede intentar enviar una solicitud a la dirección `/consultas` y
|
||||
verificar si se redirige al `ConsultaController` y comprobando el `System.out`
|
||||
que muestra los datos que llegaron en el JSON de la solicitud.
|
||||
|
||||
```json
|
||||
{
|
||||
"idPaciente": 1,
|
||||
"idMedico": 1,
|
||||
"fecha": "2023-09-14T10:00"
|
||||
}
|
||||
```
|
||||
|
||||
Esta es la ipmplementación del esqueleto de la funcionalidad. Ahora se deben
|
||||
implementar las reglas de negocio.
|
||||
|
||||
El trabajo es un poco diferente a lo ya realizado con la validación de campos
|
||||
de formulario vía ***BEAN validation***. Estas validaciones son más complejas.
|
||||
|
||||
***¿Cómo implementarlas?***
|
||||
|
||||
Observando `ConsultaController.java`, se podrían hacer todas las validaciones
|
||||
en el método `agendar()`, antes del retorno. Sin embargo, esa no es una buena
|
||||
práctica.
|
||||
|
||||
La clase controller no debe traer las reglas de negocio de la aplicación.
|
||||
Es solo una clase que controla el flujo de ejecución: cuando llega una solicitud,
|
||||
llama a la clase X, devuelve la respuesta Y. Si la condición es Z, devuelve otra
|
||||
respuesta y así sucesivamente. Es decir, solo controla el flujo de ejecución y,
|
||||
por lo tanto, no debería tener reglas de negocio.
|
||||
|
||||
Así, aislando las reglas de negocio, los algoritmos, los cálculos y las
|
||||
validaciones en otra clase que será llamada por el Controller.
|
||||
|
||||
En el paquete `consulta`. Se creas la clase para contener las reglas de agendado
|
||||
de consultas llamada
|
||||
[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java).
|
||||
|
||||
El nombre es muy autoexplicativo, esta clase contendrá la agenda de consultas.
|
||||
Se pueden tener otras funcionalidades en esta clase, relacionadas con el
|
||||
agendamiento de consultas.
|
||||
|
||||
Esta no es una clase ***Controller*** ni una clase de ***configuraciones***. Esta
|
||||
clase representa un servicio de la aplicación, el de agendado de consultas. Por
|
||||
lo tanto, será una ***clase de servicios*** (**Service**) y llevará la anotación
|
||||
`@Service`. El objetivo de esta anotación es declarar el componente de servicio
|
||||
a Spring Boot.
|
||||
|
||||
Dentro de esta clase, se crear un método `public void agendar()`, que recibe
|
||||
como parámetro el **DTO** `DatosAgendarConsulta`.
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class AgendaDeConsultaService {
|
||||
|
||||
@Autowired
|
||||
private ConsultaRepository consultaRepository;
|
||||
@Autowired
|
||||
private MedicoRepository medicoRepository;
|
||||
@Autowired
|
||||
private PacienteRepository pacienteRepository;
|
||||
|
||||
public void agendar(DatosAgendarConsulta datos) {
|
||||
|
||||
if (pacienteRepository.findById(datos.idPaciente()).isPresent()) {
|
||||
throw new ValidacionDeIntegridad("Id de paciente no encontrado");
|
||||
}
|
||||
if (datos.idMedico() != null && medicoRepository.existsById(datos.idMedico())) {
|
||||
throw new ValidacionDeIntegridad("Id de médico no encontrado");
|
||||
}
|
||||
|
||||
var paciente = pacienteRepository.findById(datos.idPaciente()).get();
|
||||
var medico = medicoRepository.findById(datos.idMedico()).get();
|
||||
var consulta = new Consulta(null, medico, paciente, datos.fecha());
|
||||
consultaRepository.save(consulta);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La clase Service ejecuta las reglas de negocio y las validaciones de la aplicación.
|
||||
|
||||
Esta clase se utliza en `ConsultaController`, con `@Autowired` se comuníca a
|
||||
Spring que instancie este objeto
|
||||
|
||||
Con esto, se inyecta la clase `AgendaDeConsultas` en el Controller. En el método
|
||||
`agendar` del `Controller`, se obtiene el objeto `agenda` y se llama al método
|
||||
`agendar()`, pasando como parámetro los datos que llegan al `Controller`.
|
||||
Todo esto antes del retorno.
|
||||
|
||||
El **Controller** recibe la información, hace solo la validación de
|
||||
***BIN validation*** y llama a la clase **Service** `AgendaDeConsultas`, que
|
||||
ejecutará las reglas de negocio. Esta es la forma correcta de manejar las reglas
|
||||
de negocio.
|
||||
|
||||
En la clase `AgendaDeConsultas` y están todas las validaciones para el método
|
||||
`agendar()`.
|
||||
|
||||
El requerimiento especifica que se debe recibir la solicitud con los datos de
|
||||
agendamiento y se deben guardar en la tabla de consultas.
|
||||
|
||||
Por lo tanto, se necesita acceder a la base de datos y a la tabla de consultas
|
||||
en esta clase. Así que se declara un atributo `ConsultaRepository`, llamándolo
|
||||
`consultaRepository`.
|
||||
|
||||
Se usa la anotación `@Autowired` para que el Spring Boot inyecte este repository
|
||||
en la clase `Service`.
|
||||
|
||||
Al final del método `agendar()`, se inserta `consultaRepository.save()` pasandole
|
||||
un objeto del tipo consulta, la entidad **JPA**. Obviamente, solo se puede llamar
|
||||
a este método si todas las validaciones se han ejecutado de acuerdo con las
|
||||
reglas de negocio.
|
||||
|
||||
La entidad `Consulta` está anotada con `@AllArgsConstructor` de ***Lombok***,
|
||||
que genera un constructor con todos los atributos. Se puede usar este mismo
|
||||
constructor en `AgendamientoDeConsultas`.
|
||||
El primer parámetro es el `Id null`, ya que es la base de datos la que pasará
|
||||
el `Id`. El segundo es `medico`, `paciente` y `fecha`. Esta última viene en el
|
||||
**DTO**, a través del parámetro `datos`.
|
||||
|
||||
Sucede que el médico y el paciente no llegan en la solicitud, sino el `Id` del
|
||||
médico y el `Id` del paciente. Por lo tanto, es necesario establecer el objeto
|
||||
completo en la entidad y no solo el `Id`.
|
||||
|
||||
Por lo tanto, es necesario cargar médico y paciente desde la base de datos.
|
||||
Se necesita inyectar, entonces, dos Repositories más en `Service`:
|
||||
`MedicoRepository` y `PacienteRepository`.
|
||||
|
||||
En el método `agendar()`, se crea un objeto paciente también. Usando
|
||||
`pacienteRepository.findById()` para buscar el objeto por `Id`, que está dentro
|
||||
del DTO datos.
|
||||
|
||||
En la solicitud solo viene el `Id`, pero se necesita cargar el objeto completo.
|
||||
Por lo tanto, se usa el Repository para cargar por el `Id` de la base de datos.
|
||||
El médico seguirá la misma dinámica (
|
||||
[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java)
|
||||
).
|
||||
|
||||
Aparecerá un error de compilación porque el método `findById()` no devuelve la
|
||||
entidad, sino un Optional. Por lo tanto, al final de la línea, antes del punto
|
||||
y coma, es necesario incluir `.get()` junto a `findById()`. Esto hace que tome
|
||||
la entidad cargada.
|
||||
|
||||
El método `agendar()` en la clase `Service` obteniene el `Id`, cargar el paciente
|
||||
y el médico desde la base de datos creando una entidad consulta pasando el médico,
|
||||
el paciente y la fecha que viene en el DTO, y se guarda en la base de datos.
|
||||
|
||||
Pero antes de esto, se necesita escribir el código para realizar todas las
|
||||
validaciones que forman parte de las reglas de negocio.
|
||||
|
||||
A continuación, se aborda cómo realizar las validaciones de la mejor manera
|
||||
posible.
|
||||
|
||||
### Validaciones
|
||||
|
||||
Para verificar el ID del paciente, se usa un `if`. La idea es comprobar si el `Id`
|
||||
del paciente existe. El Repository es el que consulta la base de datos.
|
||||
|
||||
Se puede lanzar una excepción dentro del `if`, que muestre un mensaje indicando
|
||||
el problema. Incluso se puede crear una excepción personalizada para el proyecto
|
||||
llamada `ValidacaoException()`. Con un mensaje como
|
||||
`"El ID del paciente proporcionado no existe"` o similar (
|
||||
[DatosDetalleConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java)
|
||||
).
|
||||
|
||||
En *package* `med.voll.api.infra.errores` se crea la clase
|
||||
[ValidacionDeIntegridad](./api_rest/api3/src/main/java/med/voll/api/infra/errores/ValidacionDeIntegridad.java)
|
||||
|
||||
```java
|
||||
package med.voll.api.infra.errores;
|
||||
|
||||
public class ValidacionDeIntegridad extends RuntimeException {
|
||||
public ValidacionDeIntegridad(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
En la clase
|
||||
[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java),
|
||||
se realiza la verificación con. Primero, se verifica si existe un paciente con
|
||||
el `Id` que está llegando a la base de datos. Si no existe, se lanzará una
|
||||
excepción con un mensaje de error.
|
||||
|
||||
Se utiliza el método de la ***interfaz Repository*** en Spring Data llamado
|
||||
`existsById`. Realiza una consulta a la base de datos para verificar si existe
|
||||
un registro con un determinado `Id` que devuelve un booleano, `true` si existe,
|
||||
`false` si no.
|
||||
Pasando el parámetro `datos.idMedico()`. Se niega la expresión. Con esto, si no
|
||||
hay un paciente con el `Id` en la base de datos, se debe detener la ejecución
|
||||
del resto del código.
|
||||
|
||||
Recordando la última regla de negocio.
|
||||
|
||||
***La elección del médico es opcional, y en ese caso el sistema debe elegir
|
||||
aleatoriamente algún médico disponible en la fecha/hora indicada.***
|
||||
|
||||
Por lo tanto, es posible que un `Id` de médico no llegue en la solicitud.
|
||||
No se puede llamar a `existsById` si el `Id` del médico es nulo. Esto resultará
|
||||
en un error para **JPA**.
|
||||
|
||||
Solo se puede llamar al `if` si el `Id` no es nulo. Por lo tanto, se agrega una
|
||||
condición al if antes de la condición actual:
|
||||
|
||||
```java
|
||||
if(datos.idMedico()!=null && !medicoRepository.existsById(datos.idMedico())){
|
||||
throw new ValidacionDeIntegridad("Id de medico no encontrado");
|
||||
}
|
||||
```
|
||||
|
||||
En el caso del médico, al ser un campo opcional, puede ser que la línea
|
||||
`var medico = medicoRepository.findById(dados.idMedico()).get()` tenga un
|
||||
`idMedico` nulo.
|
||||
|
||||
De acuerdo con la regla de negocio analizada, se necesita escribir un algoritmo
|
||||
que elija aleatoriamente un médico en la base de datos. Por lo tanto, la línea
|
||||
anterior necesita ser reemplazada. Pare ello se llama al método privado
|
||||
`seleccionarMedico(datos)` que recibe un objeto `DatosAgendarConsulta` como
|
||||
parametro y retorna un objeto `Medico`.
|
||||
|
||||
Esto sirve para aislar el algoritmo y evitar que esté suelto dentro del método
|
||||
de programación de citas. En el método `seleccionarMedico()`, se necesita
|
||||
verificar si está llegando el `Id` del médico en la solicitud o no.
|
||||
|
||||
Si lo está, se obtiene el médico correspondiente de la base de datos. Si no es
|
||||
así, se debe elegir aleatoriamente un profesional de la salud, según lo indica
|
||||
la regla de negocio.
|
||||
|
||||
Para implementar el algoritmo que elige al médico de manera aleatoria, se deben
|
||||
cubrir todos los posibles escenarios. La primera comprobación es que si la
|
||||
persona eligió un médico al hacer la solicitud, usando
|
||||
`if (dados.idMedico() != null)`.
|
||||
|
||||
En este caso, se carga la información de la base de datos con
|
||||
`return medicoRepository.getReferenceById(dados.idMedico())`. En lugar de usar
|
||||
`findById()`, se podemos usar `getReferenceById()` y no es necesario llamar al
|
||||
`.get()` usamdo anteriormente.
|
||||
|
||||
También se puedemo cambiar `findById()` por `getReferenceById()` en la variable
|
||||
paciente, ya que no se quiere cargar el objeto para manipularlo, sino, solo para
|
||||
asignarlo a otro objeto.
|
||||
|
||||
En el método `seleccionarMedico()` , lo primero es verificar si se realiza la
|
||||
con un médico específico para su atención. Si es así, simplemente se carga la
|
||||
información del médico que viene de la base de datos.
|
||||
|
||||
[DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
|
||||
|
||||
```java
|
||||
public record DatosAgendarConsulta(
|
||||
@NotNull Long idPaciente,
|
||||
Long idMedico,
|
||||
@NotNull @Future LocalDateTime fecha,
|
||||
Especialidad especialidad) {
|
||||
}
|
||||
```
|
||||
|
||||
***¿Cómo elegir un médico aleatorio de la especialidad elegida, disponible en
|
||||
la fecha y hora seleccionadas?***
|
||||
|
||||
Existen varias formas de hacer esto. Se podrían cargar los médicos, filtrarlos por
|
||||
especialidad y comprobar la fecha de la consulta en Java.
|
||||
|
||||
Lo ideal es cargar un profesional aleatorio directamente de la base de datos.
|
||||
Sin embargo, esta consulta es específica para nuestro proyecto, es decir, no está
|
||||
lista en Spring Data JPA.
|
||||
|
||||
Se necesita crear un método para hacer esto:
|
||||
|
||||
```java
|
||||
return medicoRepository.seleccionarMedicoConEspecialidadEnFecha(
|
||||
datos.especialidad(),datos.fecha()
|
||||
);
|
||||
```
|
||||
|
||||
Creación del método `seleccionarMedicoConEspecialidadEnFecha(especialidad, fecha)`
|
||||
en
|
||||
[MedicoRepository](./api_rest/api3/src/main/java/med/voll/api/domain/medico/MedicoRepository.java).
|
||||
|
||||
Como el nombre del método está en español. No se estam siguiendo el estándar
|
||||
de nomenclatura, como el `findAllByAtivoTrue()`. De esta manera, Spring Data no
|
||||
podrá construir el SQL automáticamente. La idea es precisamente esa, para este método,
|
||||
se escribe el comando SQL manualmente.
|
||||
|
||||
Para hacerlo, se usa la anotación `@Query()` justo encima del método. Que viene
|
||||
del paquete `org.springframework.data.jpa`. Y entre paréntesis, se construye la
|
||||
consulta utilizando la sintaxis del ***Java Persistence Query Language (JPQL)***.
|
||||
|
||||
```java
|
||||
@Query("""
|
||||
select m from Medico m
|
||||
where m.activo= true\s
|
||||
and
|
||||
m.especialidad=:especialidad\s
|
||||
and
|
||||
m.id not in( \s
|
||||
select c.medico.id from Consulta c
|
||||
where
|
||||
c.fecha=:fecha
|
||||
)
|
||||
order by rand()
|
||||
limit 1
|
||||
""")
|
||||
Medico seleccionarMedicoConEspecialidadEnFecha(
|
||||
Especialidad especialidad,
|
||||
LocalDateTime fecha
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### En resumen
|
||||
Creación de nuevo *package*
|
||||
[`domain.consulta`](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/)
|
||||
donde se crean entidad **Consulta**, clases `ConsultaRepository`,
|
||||
`DatosAgendarConsulta` y `DatosDetalleConsulta`.
|
||||
|
||||
- [Consulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java)
|
||||
- [ConsultaRepository](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java)
|
||||
- [DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
|
||||
- [DatosDetalleConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java)
|
||||
- [AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java)
|
||||
- [migración](./api_rest/api3/src/main/resources/db/migration/V6__create-table-consultas.sql)
|
||||
|
||||
---
|
||||
|
||||
### Anotación @JsonAlias
|
||||
|
||||
En caso que uno o mas campos enviados en el formato JSON a la API no correspondan
|
||||
con los atributos de las clases DTO, se pueden utilizar alias
|
||||
|
||||
```java
|
||||
public record DatosCompra(
|
||||
@JsonAlias("producto_id") Long idProducto,
|
||||
@JsonAlias("fecha_compra") LocalDate fechaCompra
|
||||
){}
|
||||
```
|
||||
|
||||
La anotación `@JsonAlias` sirve para mapear *alias* alternativos para los campos
|
||||
que se recibirán del JSON, y es posible asignar múltiples alias:
|
||||
|
||||
```java
|
||||
public record DatosCompra(
|
||||
@JsonAlias({"producto_id", "id_producto"}) Long idProducto,
|
||||
@JsonAlias({"fecha_compra", "fecha"}) LocalDate fechaCompra
|
||||
){}
|
||||
```
|
||||
|
||||
### Formato fechas
|
||||
|
||||
Spring usa un formato definido para las fechas `LocalDateTime`, sin embargo, estas
|
||||
se pueden personalizar. Por ejemplo, para aceptar el formato `dd/mm/yyy hh:mm`.
|
||||
Para ello se utiliza la anotación `@JsonFormat`
|
||||
|
||||
```java
|
||||
@NotNull
|
||||
@Future
|
||||
@JsonFormat(pattern = "dd/MM/yyyy HH:mm")
|
||||
LocalDateTime fecha
|
||||
```
|
||||
|
||||
Esta anotación también se puede utilizar en las clases DTO que representan la
|
||||
información que devuelve la API, para que el JSON devuelto se formatee de
|
||||
acuerdo con el patrón configurado.
|
||||
Además, no se limita solo a la clase `LocalDateTime`, sino que también se puede
|
||||
utilizar en atributos de tipo `LocalDate` y `LocalTime`.
|
||||
|
||||
---
|
||||
|
||||
### Patrón Service
|
||||
|
||||
El ***Service pattern*** es muy utilizado en la programación y su nombre es muy
|
||||
conocido. Pero a pesar de ser un nombre único, **Service** puede ser interpretado
|
||||
de varias maneras:
|
||||
|
||||
- Puede ser un caso de uso, **Application Service**
|
||||
- Un **Domain Service**, que tiene reglas de su dominio
|
||||
- Un **Infrastructure Service**, que utiliza algún paquete externo para realizar
|
||||
tareas
|
||||
- etc
|
||||
|
||||
A pesar de que la interpretación puede ocurrir de varias formas, la idea detrás
|
||||
del patrón es separar las reglas de negocio, las reglas de la aplicación y las
|
||||
reglas de presentación para que puedan ser fácilmente probadas y reutilizadas
|
||||
en otras partes del sistema.
|
||||
|
||||
Existen dos formas más utilizadas para crear **Services**. Puede crear
|
||||
**Services** más genéricos, responsables de todas las asignaciones de un
|
||||
**Controller**. O ser aún más específico, aplicando así la S del ***SOLID***:
|
||||
***Single Responsibility Principle*** (*Principio de Responsabilidad Única*).
|
||||
Este principio nos dice que una clase/función/archivo debe tener sólo una
|
||||
única responsabilidad.
|
||||
|
||||
Piense en un sistema de ventas, en el que probablemente tendríamos algunas
|
||||
funciones como:
|
||||
|
||||
- Registrar usuario
|
||||
- Iniciar sesión
|
||||
- Buscar productos
|
||||
- Buscar producto por nombre
|
||||
- etc
|
||||
|
||||
Entonces, se podrían crear los siguientes **Services**:
|
||||
|
||||
- RegistroDeUsuarioService
|
||||
- IniciarSesionService
|
||||
- BusquedaDeProductosService
|
||||
- etc
|
||||
|
||||
Pero es importante estar atentos, ya que muchas veces no es necesario crear un
|
||||
**Service** y, por lo tanto, agregar otra capa y complejidad innecesarias a
|
||||
una aplicación. Una regla que podemos utilizar es la siguiente: si no hay reglas
|
||||
de negocio, simplemente se puede realizar la comunicación directa entre los
|
||||
controllers y los repositories de la aplicación.
|
||||
|
||||
---
|
||||
|
||||
|
@ -52,3 +52,4 @@ primoridiales en programación con Javascript
|
||||
- [JPA consultas avanzadas, rendimiento y modelos complejos](./010_spring_boot/jpa_avanzado.md)
|
||||
- [Desarrollo API Rest](./010_spring_boot/spring_boot_1.md)
|
||||
- [Buenas prácticas y protección de API Rest](./010_spring_boot/spring_boot_2.md)
|
||||
- [Docs, Tests y preparación para implementacion](./010_spring_boot/spring_boot_3.md)
|
||||
|
Loading…
Reference in New Issue
Block a user