diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java index 76e6165..38f88aa 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java @@ -1,18 +1,14 @@ package med.voll.api.controller; +import jakarta.transaction.Transactional; import jakarta.validation.Valid; -import med.voll.api.medico.DatosListadoMedicos; -import med.voll.api.medico.DatosRegistroMedico; -import med.voll.api.medico.Medico; -import med.voll.api.medico.MedicoRepository; +import med.voll.api.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.web.bind.annotation.*; -import java.util.List; - @RestController @RequestMapping("/medicos") public class MedicoController { @@ -28,6 +24,29 @@ public class MedicoController { @GetMapping public Page listadoMedicos(@PageableDefault(size = 5) Pageable paginacion) { - return medicoRepository.findAll(paginacion).map(DatosListadoMedicos::new); + //return medicoRepository.findAll(paginacion).map(DatosListadoMedicos::new); + return medicoRepository.findByActivoTrue(paginacion).map(DatosListadoMedicos::new); } + + @PutMapping + @Transactional + public void actualizarMedico(@RequestBody @Valid DatosActualizarMedico datosActualizarMedico) { + Medico medico = medicoRepository.getReferenceById(datosActualizarMedico.id()); + medico.actualizarDatos(datosActualizarMedico); + } + + // Desactivar Medico + @DeleteMapping("/{id}") + @Transactional + public void eliminarMedico(@PathVariable Long id) { + Medico medico = medicoRepository.getReferenceById(id); + medico.desactivarMedico(); + } + + // Eliminar de la base de datos + //public void eliminarMedico(@PathVariable Long id) { + // Medico medico = medicoRepository.getReferenceById(id); + // medicoRepository.delete(medico); + //} + } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/PacienteController.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/PacienteController.java index dd39d75..afd0fb2 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/PacienteController.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/PacienteController.java @@ -1,10 +1,8 @@ package med.voll.api.controller; +import jakarta.transaction.Transactional; import jakarta.validation.Valid; -import med.voll.api.paciente.DatosListadoPacientes; -import med.voll.api.paciente.DatosRegistroPaciente; -import med.voll.api.paciente.Paciente; -import med.voll.api.paciente.PacienteRepository; +import med.voll.api.paciente.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -26,6 +24,22 @@ public class PacienteController { @GetMapping public Page listadoPacientes(@PageableDefault(size = 5) Pageable paginacion) { - return pacienteRepository.findAll(paginacion).map(DatosListadoPacientes::new); + //return pacienteRepository.findAll(paginacion).map(DatosListadoPacientes::new); + return pacienteRepository.findByActivoTrue(paginacion).map(DatosListadoPacientes::new); + } + + @PutMapping + @Transactional + public void actualizarPaciente(@RequestBody @Valid DatosActualizarPaciente datosActualizarPaciente) { + Paciente paciente = pacienteRepository.getReferenceById(datosActualizarPaciente.id()); + paciente.actualizarDatos(datosActualizarPaciente); + } + + // Desactivar Paciente + @DeleteMapping("/{id}") + @Transactional + public void eliminarPaciente(@PathVariable Long id) { + Paciente paciente = pacienteRepository.getReferenceById(id); + paciente.desactivarPaciente(); } } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/Direccion.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/Direccion.java index 23488c7..1eb126f 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/Direccion.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/Direccion.java @@ -24,4 +24,13 @@ public class Direccion { 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; + } } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosActualizarMedico.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosActualizarMedico.java new file mode 100644 index 0000000..5196c22 --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosActualizarMedico.java @@ -0,0 +1,8 @@ +package med.voll.api.medico; + +import jakarta.validation.constraints.NotNull; +import med.voll.api.direccion.DatosDireccion; + +public record DatosActualizarMedico(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) { + +} diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosListadoMedicos.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosListadoMedicos.java index 60a3c89..7075072 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosListadoMedicos.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosListadoMedicos.java @@ -1,9 +1,14 @@ package med.voll.api.medico; -public record DatosListadoMedicos(String nombre, String especialidad, String documento, String email) { +public record DatosListadoMedicos( + Long id, + String nombre, + String especialidad, + String documento, String email) { public DatosListadoMedicos (Medico medico) { - this(medico.getNombre(), + this(medico.getId(), + medico.getNombre(), medico.getEspecialidad().toString(), medico.getDocumento(), medico.getEmail()); diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/Medico.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/Medico.java index b7cfad3..0473c67 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/Medico.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/Medico.java @@ -22,12 +22,14 @@ public class Medico { 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(); @@ -35,4 +37,21 @@ public class Medico { 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; + } + } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/MedicoRepository.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/MedicoRepository.java index d667391..dda5f94 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/MedicoRepository.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/MedicoRepository.java @@ -1,6 +1,9 @@ package med.voll.api.medico; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface MedicoRepository extends JpaRepository { + Page findByActivoTrue(Pageable paginacion); } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/DatosActualizarPaciente.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/DatosActualizarPaciente.java new file mode 100644 index 0000000..992de19 --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/DatosActualizarPaciente.java @@ -0,0 +1,8 @@ +package med.voll.api.paciente; + +import jakarta.validation.constraints.NotNull; +import med.voll.api.direccion.DatosDireccion; + +public record DatosActualizarPaciente(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) { + +} diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/DatosListadoPacientes.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/DatosListadoPacientes.java index cc29f2f..1c69a63 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/DatosListadoPacientes.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/DatosListadoPacientes.java @@ -1,11 +1,12 @@ package med.voll.api.paciente; -public record DatosListadoPacientes(String nombre, String documento, String email) { +public record DatosListadoPacientes(Long id, String nombre, String documento, String email) { - public DatosListadoPacientes(Paciente medico) { - this(medico.getNombre(), - medico.getDocumento(), - medico.getEmail()); + public DatosListadoPacientes(Paciente paciente) { + this(paciente.getId(), + paciente.getNombre(), + paciente.getDocumento(), + paciente.getEmail()); } } \ No newline at end of file diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/Paciente.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/Paciente.java index d7993f1..4148c8e 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/Paciente.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/Paciente.java @@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import med.voll.api.direccion.Direccion; +import med.voll.api.medico.DatosActualizarMedico; @Table(name="pacientes") @Entity(name="Paciente") @@ -22,14 +23,33 @@ public class Paciente { 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; + } + } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/PacienteRepository.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/PacienteRepository.java index da767e7..46e0e20 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/PacienteRepository.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/paciente/PacienteRepository.java @@ -1,6 +1,9 @@ package med.voll.api.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 { + Page findByActivoTrue(Pageable paginacion); } diff --git a/010_spring_boot/api_rest/api/src/main/resources/db/migration/V4__alter-table-medicos-add-activo.sql b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V4__alter-table-medicos-add-activo.sql new file mode 100644 index 0000000..7b42ad7 --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V4__alter-table-medicos-add-activo.sql @@ -0,0 +1,2 @@ +alter table medicos add activo tinyint; +update medicos set activo=1; \ No newline at end of file diff --git a/010_spring_boot/api_rest/api/src/main/resources/db/migration/V5__alter-table-pacientes-add-activo.sql b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V5__alter-table-pacientes-add-activo.sql new file mode 100644 index 0000000..68589f4 --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V5__alter-table-pacientes-add-activo.sql @@ -0,0 +1,2 @@ +alter table pacientes add activo tinyint; +update pacientes set activo=1; \ No newline at end of file diff --git a/010_spring_boot/spring_boot_1.md b/010_spring_boot/spring_boot_1.md index 3d905b8..5d61af3 100644 --- a/010_spring_boot/spring_boot_1.md +++ b/010_spring_boot/spring_boot_1.md @@ -605,8 +605,8 @@ Sin embargo, puede haber algún otro endpoint de la API en el que necesitemos enviar el salario de los empleados en el JSON, en cuyo caso se tendrían problemas, ya que con la anotación `@JsonIgnore` tal atributo nunca se enviará en el JSON, y al eliminar la anotación se enviará el atributo siempre. Por lo tanto, -se pierde la flexibilidad de controlar cuándo se deben enviar ciertos atributos -en el JSON y cuándo no. +se pierde la flexibilidad de controlar cuando se deben enviar ciertos atributos +en el JSON y cuando no. #### DTO @@ -721,3 +721,157 @@ spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true ``` +### Put + +Información permitida para actualización + +- Nombre +- Documento +- Direccion + +### Mass Assignment Attack + +Ataque de asignación masiva, ocurre cuando un usuario logra inicializar o +reemplazar parámetros que no deben ser modificados en la aplicación. +Al incluir parámetros adicionales en una solicitud, si dichos parámetros son +válidos, un usuario malintencionado puede generar un efecto secundario no deseado +en la aplicación. + +El concepto de este ataque se refiere a cuando se inyecta un conjunto de valores +directamente en un objeto, de ahí la asignación masiva de nombres, que, sin la +debida validación puede causar serios problemas. + +Ejemplo práctico. Se tiene el siguiente método, en una clase Controller, +utilizado para registrar un usuario en la aplicación: + +```java +@PostMapping +@Transactional +public void registrar(@RequestBody @Valid Usuario usuario) { + repository.save(usuario); +} +``` + +Y la entidad JPA que representa al usuario: + +```java +@Getter +@Setter +@NoArgsConstructor +@EqualsAndHashCode(of = "id") +@Entity(name = "Usuario") +@Table(name = "usuarios") +public class Usuario { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String nombre; + private String email; + private Boolean admin = false; + + ... +} +``` + +Notar que el atributo admin de la clase `Usuario` se inicializa como falso, lo +que indica que un usuario siempre debe estar registrado como administrador. Sin +embargo, si se envía el siguiente JSON en la solicitud: + +```json +{ + "nombre" : "Rodrigo", + "email" : "rodrigo@email.com", + "admin" : true +} +``` + +El usuario se registrará con el atributo `admin` con valor `true`. Esto sucede +porque el atributo `admin` enviado en el JSON existe en la clase que se está +recibiendo en el Controller, considerándose un atributo válido y que se llenará +en el objeto `Usuario` que será instanciado por Spring. + +**¿Cómo prevenir este problema?** + +El uso del patrón **DTO** ayuda a evitar este problema, ya que al crear un DTO +se definen solo los campos que se pueden recibir en la API, y en el ejemplo +anterior el DTO no tendría el atributo admin. + +Esta es una ventaja más de usar el patrón DTO para representar los datos que +entran y salen de la API. + +### PATCH + +Elegir entre el método HTTP **PUT** o **PATCH** es una pregunta común que surge +al desarrollar APIs y se necesita crear un endpoint para la actualización de +recursos. + +#### Diferencias entre las dos opciones y cuando usar cada una + +- **PUT** +Reemplaza todos los datos actuales de un recurso con los datos enviados en la +solicitud, es decir, una actualización completa de un recurso en una sola +solicitud. + +- **PATCH** +Aplica modificaciones parciales a un recurso. Por lo tanto, es posible modificar +solo una parte de un recurso, lo que flexibiliza las opciones de actualización. + +**¿Cuál elegir?** + +En la práctica, es difícil saber qué método usar, porque no siempre se sabrá +si un recurso se actualizará parcial o completamente en una solicitud, a menos +que lo verifiquemos, algo que no se recomienda. + +Entonces, lo más común en las aplicaciones es usar el método PUT para las +solicitudes de actualización de recursos en una API, que es la elección para +el proyecto ***voll med api***. + +### Delete + +Exclusión de Médicos + +- El registro no debe ser borrado de la base de datos +- El listada debe retornar solo Médicos activos + + +Mapeo de solicitude PUT con la anotación `@PutMapping` + +```java + @PutMapping + @Transactional + public void actualizarMedico(@RequestBody @Valid DatosActualizarMedico datosActualizarMedico) { + Medico medico = medicoRepository.getReferenceById(datosActualizarMedico.id()); + medico.actualizarDatos(datosActualizarMedico); + } +``` + +Mapeo de solicitud **DELETE** con la anotación `@DeleteMapping`. +Y mapeo de parámetros dinámicos en la URL con la anotación `@PathVariable`. + +```java + @DeleteMapping("/{id}") + @Transactional + public void eliminarMedico(@PathVariable Long id) { + Medico medico = medicoRepository.getReferenceById(id); + medico.desactivarMedico(); + } +``` + +```java +package med.voll.api.medico; + +import jakarta.validation.constraints.NotNull; +import med.voll.api.direccion.DatosDireccion; + +public record DatosActualizarMedico( + @NotNull Long id, + String nombre, + String documento, + DatosDireccion direccion +) {} +``` + +---- + +Continua en [Buenas prácticas y protección de una API Rest](./spring_boot_2.md)