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;
|
||||
|
||||
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<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;
|
||||
|
||||
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<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.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;
|
||||
|
||||
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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<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;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<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,
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user