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()
|
.and()
|
||||||
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
.build();
|
.build();
|
||||||
//return httpSecurity.csrf().disable()
|
|
||||||
// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
|
||||||
// .and().authorizeHttpRequests()
|
|
||||||
// .requestMatchers(HttpMethod.POST, "/login").permitAll()
|
|
||||||
// .anyRequest().authenticated()
|
|
||||||
// .and().build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -30,10 +30,10 @@ public class SecurityFilter extends OncePerRequestFilter {
|
|||||||
if (authHeader != null) {
|
if (authHeader != null) {
|
||||||
System.out.println("- ".repeat(10) + "filtro no null"+" -".repeat(10));
|
System.out.println("- ".repeat(10) + "filtro no null"+" -".repeat(10));
|
||||||
var token = authHeader.replace("Bearer ", "");
|
var token = authHeader.replace("Bearer ", "");
|
||||||
var subject = tokenService.getSubject(token);
|
var nombreUsuario = tokenService.getSubject(token);
|
||||||
if (subject != null) {
|
if (nombreUsuario != null) {
|
||||||
// token válido
|
// token válido
|
||||||
var usuario = usuarioRepository.findByLogin(subject);
|
var usuario = usuarioRepository.findByLogin(nombreUsuario);
|
||||||
var authentication = new UsernamePasswordAuthenticationToken(
|
var authentication = new UsernamePasswordAuthenticationToken(
|
||||||
usuario,
|
usuario,
|
||||||
null,
|
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
|
documentación de
|
||||||
[Spring Security](https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html)
|
[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)
|
- [JPA consultas avanzadas, rendimiento y modelos complejos](./010_spring_boot/jpa_avanzado.md)
|
||||||
- [Desarrollo API Rest](./010_spring_boot/spring_boot_1.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)
|
- [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