Spring Boot 3 - Desarrollo API Rest: Aula 5 FIN
This commit is contained in:
parent
c391edc321
commit
7afd8de538
@ -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);
|
||||||
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user