Spring Boot 3 - Desarrollo API Rest: Aula 5 FIN

This commit is contained in:
devfzn 2023-09-11 03:12:09 -03:00
parent c391edc321
commit 7afd8de538
Signed by: devfzn
GPG Key ID: E070ECF4A754FDB1
14 changed files with 288 additions and 21 deletions

View File

@ -1,18 +1,14 @@
package med.voll.api.controller; package med.voll.api.controller;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import med.voll.api.medico.DatosListadoMedicos; import med.voll.api.medico.*;
import med.voll.api.medico.DatosRegistroMedico;
import med.voll.api.medico.Medico;
import med.voll.api.medico.MedicoRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault; import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController @RestController
@RequestMapping("/medicos") @RequestMapping("/medicos")
public class MedicoController { public class MedicoController {
@ -28,6 +24,29 @@ public class MedicoController {
@GetMapping @GetMapping
public Page<DatosListadoMedicos> listadoMedicos(@PageableDefault(size = 5) Pageable paginacion) { public Page<DatosListadoMedicos> 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);
//}
} }

View File

@ -1,10 +1,8 @@
package med.voll.api.controller; package med.voll.api.controller;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import med.voll.api.paciente.DatosListadoPacientes; import med.voll.api.paciente.*;
import med.voll.api.paciente.DatosRegistroPaciente;
import med.voll.api.paciente.Paciente;
import med.voll.api.paciente.PacienteRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@ -26,6 +24,22 @@ public class PacienteController {
@GetMapping @GetMapping
public Page<DatosListadoPacientes> listadoPacientes(@PageableDefault(size = 5) Pageable paginacion) { public Page<DatosListadoPacientes> 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();
} }
} }

View File

@ -24,4 +24,13 @@ public class Direccion {
this.distrito = direccion.distrito(); this.distrito = direccion.distrito();
this.ciudad = direccion.ciudad(); 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;
}
} }

View File

@ -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) {
}

View File

@ -1,9 +1,14 @@
package med.voll.api.medico; 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) { public DatosListadoMedicos (Medico medico) {
this(medico.getNombre(), this(medico.getId(),
medico.getNombre(),
medico.getEspecialidad().toString(), medico.getEspecialidad().toString(),
medico.getDocumento(), medico.getDocumento(),
medico.getEmail()); medico.getEmail());

View File

@ -22,12 +22,14 @@ public class Medico {
private String email; private String email;
private String telefono; private String telefono;
private String documento; private String documento;
private Boolean activo;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private Especialidad especialidad; private Especialidad especialidad;
@Embedded @Embedded
private Direccion direccion; private Direccion direccion;
public Medico(DatosRegistroMedico datosRegistroMedico) { public Medico(DatosRegistroMedico datosRegistroMedico) {
this.activo = true;
this.nombre = datosRegistroMedico.nombre(); this.nombre = datosRegistroMedico.nombre();
this.email = datosRegistroMedico.email(); this.email = datosRegistroMedico.email();
this.documento = datosRegistroMedico.documento(); this.documento = datosRegistroMedico.documento();
@ -35,4 +37,21 @@ public class Medico {
this.especialidad = datosRegistroMedico.especialidad(); this.especialidad = datosRegistroMedico.especialidad();
this.direccion = new Direccion(datosRegistroMedico.direccion()); 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;
}
} }

View File

@ -1,6 +1,9 @@
package med.voll.api.medico; package med.voll.api.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.JpaRepository;
public interface MedicoRepository extends JpaRepository<Medico, Long> { public interface MedicoRepository extends JpaRepository<Medico, Long> {
Page<Medico> findByActivoTrue(Pageable paginacion);
} }

View File

@ -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) {
}

View File

@ -1,11 +1,12 @@
package med.voll.api.paciente; 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) { public DatosListadoPacientes(Paciente paciente) {
this(medico.getNombre(), this(paciente.getId(),
medico.getDocumento(), paciente.getNombre(),
medico.getEmail()); paciente.getDocumento(),
paciente.getEmail());
} }
} }

View File

@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import med.voll.api.direccion.Direccion; import med.voll.api.direccion.Direccion;
import med.voll.api.medico.DatosActualizarMedico;
@Table(name="pacientes") @Table(name="pacientes")
@Entity(name="Paciente") @Entity(name="Paciente")
@ -22,14 +23,33 @@ public class Paciente {
private String email; private String email;
private String telefono; private String telefono;
private String documento; private String documento;
private Boolean activo;
@Embedded @Embedded
private Direccion direccion; private Direccion direccion;
public Paciente(DatosRegistroPaciente datosRegistroPaciente) { public Paciente(DatosRegistroPaciente datosRegistroPaciente) {
this.activo = true;
this.nombre = datosRegistroPaciente.nombre(); this.nombre = datosRegistroPaciente.nombre();
this.email = datosRegistroPaciente.email(); this.email = datosRegistroPaciente.email();
this.documento = datosRegistroPaciente.documento(); this.documento = datosRegistroPaciente.documento();
this.telefono = datosRegistroPaciente.telefono(); this.telefono = datosRegistroPaciente.telefono();
this.direccion = new Direccion(datosRegistroPaciente.direccion()); 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;
}
} }

View File

@ -1,6 +1,9 @@
package med.voll.api.paciente; package med.voll.api.paciente;
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.JpaRepository;
public interface PacienteRepository extends JpaRepository<Paciente, Long> { public interface PacienteRepository extends JpaRepository<Paciente, Long> {
Page<Paciente> findByActivoTrue(Pageable paginacion);
} }

View File

@ -0,0 +1,2 @@
alter table medicos add activo tinyint;
update medicos set activo=1;

View File

@ -0,0 +1,2 @@
alter table pacientes add activo tinyint;
update pacientes set activo=1;

View File

@ -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, 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, 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, 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 se pierde la flexibilidad de controlar cuando se deben enviar ciertos atributos
en el JSON y cuándo no. en el JSON y cuando no.
#### DTO #### DTO
@ -721,3 +721,157 @@ spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_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)