From c6c724b23fdd2cad1716427ac2a752b1cc34f532 Mon Sep 17 00:00:00 2001 From: LuisDev18 Date: Wed, 30 Jul 2025 20:46:40 -0500 Subject: [PATCH 1/2] fix: remove duplicate code findById --- src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java b/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java index 5b7fb2e..7e2d52e 100644 --- a/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java +++ b/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java @@ -54,8 +54,7 @@ public Usuario update(UsuarioRequestDto usuario, Integer id) { usuarioRepository.findByEmail(usuario.getEmail()).orElseThrow( () -> new EmailAlreadyException("El email: %s ya esta registrado".formatted(usuario.getEmail()))); - Usuario registro = usuarioRepository.findById(id).orElseThrow( - () -> new NoDataFoundException("No existe el registro con el id: %d".formatted(id))); + Usuario registro = findById(id); registro.setRol(Rol.valueOf(usuario.getRol())); registro.setEmail(usuario.getEmail()); registro.setPassword(encoder.encode(usuario.getPassword())); From e2e1b84c13eea5af7a09a49346a06005b3ada243 Mon Sep 17 00:00:00 2001 From: LuisDev18 Date: Wed, 30 Jul 2025 20:59:58 -0500 Subject: [PATCH 2/2] fix: apply spotless format --- build.gradle | 2 +- .../pe/edu/utp/ArticulosapiApplication.java | 6 +- .../utp/controller/ArticuloController.java | 47 ++-- .../edu/utp/controller/UsuarioController.java | 48 ++-- .../edu/utp/converter/ArticuloConverter.java | 32 +-- .../converter/ArticuloResponseConverter.java | 35 +-- .../edu/utp/converter/UsuarioConverter.java | 28 +-- src/main/java/pe/edu/utp/dto/ArticuloDto.java | 16 +- .../pe/edu/utp/dto/ArticuloResponseDto.java | 1 - .../java/pe/edu/utp/dto/LoginRequestDto.java | 1 + .../java/pe/edu/utp/dto/LoginResponseDto.java | 1 + .../pe/edu/utp/dto/UsuarioRequestDto.java | 2 +- .../pe/edu/utp/dto/UsuarioResponseDto.java | 12 +- src/main/java/pe/edu/utp/entity/Articulo.java | 3 +- src/main/java/pe/edu/utp/entity/Rol.java | 2 +- src/main/java/pe/edu/utp/entity/Usuario.java | 9 +- .../utp/exception/EmailAlreadyException.java | 13 +- .../utp/exception/GlobalExceptionHandler.java | 206 +++++++++++------- .../utp/exception/NoDataFoundException.java | 1 - .../utp/repository/ArticuloRepository.java | 20 +- .../edu/utp/repository/UsuarioRepository.java | 1 - .../security/CustomerUserDetailsService.java | 29 +-- .../utp/security/JwtAuthenticationFilter.java | 150 +++++++------ .../java/pe/edu/utp/security/JwtService.java | 33 +-- .../java/pe/edu/utp/security/PemReader.java | 25 +-- .../utp/security/SecurityConfiguration.java | 95 ++++---- .../pe/edu/utp/service/ArticuloService.java | 22 +- .../edu/utp/service/ArticuloServiceImpl.java | 141 ++++++------ .../pe/edu/utp/service/UsuarioService.java | 21 +- .../edu/utp/service/UsuarioServiceImpl.java | 142 ++++++------ src/main/java/pe/edu/utp/util/ApiError.java | 7 +- .../java/pe/edu/utp/util/ApiResponse.java | 122 +++++++---- .../edu/utp/ArticulosapiApplicationTests.java | 14 +- .../edu/utp/service/UsuarioServiceTest.java | 32 +-- 34 files changed, 725 insertions(+), 594 deletions(-) diff --git a/build.gradle b/build.gradle index 7ceb008..d3a1b22 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { group = 'pe.edu.utp' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '21' // Actualizado a Java 21 +sourceCompatibility = '21' configurations { compileOnly { diff --git a/src/main/java/pe/edu/utp/ArticulosapiApplication.java b/src/main/java/pe/edu/utp/ArticulosapiApplication.java index 171f7e4..c8855a2 100644 --- a/src/main/java/pe/edu/utp/ArticulosapiApplication.java +++ b/src/main/java/pe/edu/utp/ArticulosapiApplication.java @@ -8,7 +8,7 @@ @EnableJpaAuditing public class ArticulosapiApplication { - public static void main(String[] args) { - SpringApplication.run(ArticulosapiApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(ArticulosapiApplication.class, args); + } } diff --git a/src/main/java/pe/edu/utp/controller/ArticuloController.java b/src/main/java/pe/edu/utp/controller/ArticuloController.java index 6695f86..d31aa28 100644 --- a/src/main/java/pe/edu/utp/controller/ArticuloController.java +++ b/src/main/java/pe/edu/utp/controller/ArticuloController.java @@ -3,9 +3,11 @@ import java.util.List; import java.util.Map; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -18,9 +20,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import pe.edu.utp.dto.ArticuloDto; import pe.edu.utp.dto.ArticuloResponseDto; import pe.edu.utp.entity.Articulo; @@ -38,20 +37,26 @@ public class ArticuloController { @GetMapping public ResponseEntity>> getAll( - - @RequestParam(value = "marca", required = false) String marca, - @RequestParam(value = "categoria", required = false) String categoria, - @RequestParam(value = "precioMin", required = false) Double precioMin, - @RequestParam(value = "precioMax", required = false) Double precioMax, - @RequestParam(value = "offset", required = false, defaultValue = "0") int pageNumber, - @RequestParam(value = "limit", required = false, defaultValue = "5") int pageSize) { - + @RequestParam(value = "marca", required = false) String marca, + @RequestParam(value = "categoria", required = false) String categoria, + @RequestParam(value = "precioMin", required = false) Double precioMin, + @RequestParam(value = "precioMax", required = false) Double precioMax, + @RequestParam(value = "offset", required = false, defaultValue = "0") int pageNumber, + @RequestParam(value = "limit", required = false, defaultValue = "5") int pageSize + ) { Pageable pageable = PageRequest.of(pageNumber, pageSize); List articulos; - if (marca !=null || categoria != null || precioMin != null || precioMax != null) { - articulos = articuloService.findByCategoriaAndMarcaAndPrecio(categoria, marca, precioMin, precioMax, pageable); + if (marca != null || categoria != null || precioMin != null || precioMax != null) { + articulos = + articuloService.findByCategoriaAndMarcaAndPrecio( + categoria, + marca, + precioMin, + precioMax, + pageable + ); } else { - articulos = articuloService.findAll(pageable); + articulos = articuloService.findAll(pageable); } return ApiResponse.ok(articulos).toResponseEntity(); } @@ -71,21 +76,25 @@ public ResponseEntity> create(@Valid @RequestBody Articulo @PutMapping(value = "/{id}") public ResponseEntity update( - @PathVariable("id") Integer id, @Valid @RequestBody ArticuloDto articuloDto) { + @PathVariable("id") Integer id, + @Valid @RequestBody ArticuloDto articuloDto + ) { Articulo articuloUpdate = articuloService.update(articuloDto, id); return ResponseEntity.ok(articuloUpdate); } @PatchMapping(value = "/{id}") - public ResponseEntity> partialUpdate(@PathVariable("id") Integer id, @RequestBody Map fields) { + public ResponseEntity> partialUpdate( + @PathVariable("id") Integer id, + @RequestBody Map fields + ) { var articuloUpdate = articuloService.partialUpdate(id, fields); return ApiResponse.ok(articuloUpdate).toResponseEntity(); } - @DeleteMapping(value = "/{id}") public ResponseEntity delete(@PathVariable("id") Integer id) - throws NoDataFoundException { + throws NoDataFoundException { articuloService.delete(id); return ResponseEntity.ok(null); } diff --git a/src/main/java/pe/edu/utp/controller/UsuarioController.java b/src/main/java/pe/edu/utp/controller/UsuarioController.java index ea46709..ea1f5f8 100644 --- a/src/main/java/pe/edu/utp/controller/UsuarioController.java +++ b/src/main/java/pe/edu/utp/controller/UsuarioController.java @@ -2,9 +2,9 @@ import java.util.List; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import lombok.RequiredArgsConstructor; import pe.edu.utp.converter.UsuarioConverter; import pe.edu.utp.dto.LoginRequestDto; import pe.edu.utp.dto.LoginResponseDto; @@ -32,45 +31,50 @@ public class UsuarioController { private final UsuarioService usuarioService; private final UsuarioConverter converter; - -@GetMapping("/usuarios") - public ResponseEntity>> findAll( - @RequestParam(value = "email", required = false) String email, - @RequestParam(value = "offset", required = false, defaultValue = "0") int pageNumber, - @RequestParam(value = "limit", required = false, defaultValue = "10") int pageSize) throws Exception { - - Pageable pagina = PageRequest.of(pageNumber, pageSize); - List registros = usuarioService.findAll(pagina); - List registrosDTO = converter.fromEntity(registros); - return ApiResponse.ok("Users retrieved successfully", registrosDTO).toResponseEntity(); - } + @GetMapping("/usuarios") + public ResponseEntity>> findAll( + @RequestParam(value = "email", required = false) String email, + @RequestParam(value = "offset", required = false, defaultValue = "0") int pageNumber, + @RequestParam(value = "limit", required = false, defaultValue = "10") int pageSize + ) throws Exception { + Pageable pagina = PageRequest.of(pageNumber, pageSize); + List registros = usuarioService.findAll(pagina); + List registrosDTO = converter.fromEntity(registros); + return ApiResponse.ok("Users retrieved successfully", registrosDTO).toResponseEntity(); + } @PostMapping("/usuarios-register") - public ResponseEntity> create(@RequestBody UsuarioRequestDto usuario) { + public ResponseEntity> create( + @RequestBody UsuarioRequestDto usuario + ) { Usuario registro = usuarioService.save(usuario); - return ApiResponse.created("User created successfully", converter.fromEntity(registro)).toResponseEntity(); + return ApiResponse + .created("User created successfully", converter.fromEntity(registro)) + .toResponseEntity(); } @PutMapping(value = "/usuarios/{id}") public ResponseEntity> update( - @PathVariable("id") int id, @RequestBody UsuarioRequestDto usuario) { + @PathVariable("id") int id, + @RequestBody UsuarioRequestDto usuario + ) { Usuario registro = usuarioService.update(usuario, id); if (registro == null) { return ResponseEntity.notFound().build(); } - return ApiResponse.ok("User updated successfully", converter.fromEntity(registro)).toResponseEntity(); + return ApiResponse + .ok("User updated successfully", converter.fromEntity(registro)) + .toResponseEntity(); } @DeleteMapping(value = "/usuarios/{id}") public ResponseEntity> delete(@PathVariable("id") int id) { usuarioService.delete(id); - return ApiResponse.noContent().toResponseEntity(); + return ApiResponse.noContent().toResponseEntity(); } - @PostMapping(value = "/usuarios/login") - public ResponseEntity> login( - @RequestBody LoginRequestDto request) { + public ResponseEntity> login(@RequestBody LoginRequestDto request) { LoginResponseDto response = usuarioService.login(request); return ApiResponse.ok("Login successfully", response).toResponseEntity(); } diff --git a/src/main/java/pe/edu/utp/converter/ArticuloConverter.java b/src/main/java/pe/edu/utp/converter/ArticuloConverter.java index 96479ce..ba3a80d 100644 --- a/src/main/java/pe/edu/utp/converter/ArticuloConverter.java +++ b/src/main/java/pe/edu/utp/converter/ArticuloConverter.java @@ -1,6 +1,7 @@ package pe.edu.utp.converter; import org.springframework.stereotype.Component; + import pe.edu.utp.dto.ArticuloDto; import pe.edu.utp.entity.Articulo; @@ -9,17 +10,17 @@ public class ArticuloConverter extends AbstractConverter @Override public ArticuloDto fromEntity(Articulo entity) { - if (entity == null) { return null; } else { - return ArticuloDto.builder() - .nombre(entity.getNombre()) - .precio(entity.getPrecio()) - .marca(entity.getMarca()) - .categoria(entity.getCategoria()) - .stock(entity.getStock()) - .build(); + return ArticuloDto + .builder() + .nombre(entity.getNombre()) + .precio(entity.getPrecio()) + .marca(entity.getMarca()) + .categoria(entity.getCategoria()) + .stock(entity.getStock()) + .build(); } } @@ -28,13 +29,14 @@ public Articulo fromDto(ArticuloDto dto) { if (dto == null) { return null; } else { - return Articulo.builder() - .nombre(dto.getNombre()) - .precio(dto.getPrecio()) - .marca(dto.getMarca()) - .categoria(dto.getCategoria()) - .stock(dto.getStock()) - .build(); + return Articulo + .builder() + .nombre(dto.getNombre()) + .precio(dto.getPrecio()) + .marca(dto.getMarca()) + .categoria(dto.getCategoria()) + .stock(dto.getStock()) + .build(); } } } diff --git a/src/main/java/pe/edu/utp/converter/ArticuloResponseConverter.java b/src/main/java/pe/edu/utp/converter/ArticuloResponseConverter.java index 5b8ada1..a93ddc1 100644 --- a/src/main/java/pe/edu/utp/converter/ArticuloResponseConverter.java +++ b/src/main/java/pe/edu/utp/converter/ArticuloResponseConverter.java @@ -13,14 +13,15 @@ public ArticuloResponseDto fromEntity(Articulo entity) { if (entity == null) { return null; } else { - return ArticuloResponseDto.builder() - .id(entity.getId()) - .nombre(entity.getNombre()) - .precio(entity.getPrecio()) - .marca(entity.getMarca()) - .categoria(entity.getCategoria()) - .stock(entity.getStock()) - .build(); + return ArticuloResponseDto + .builder() + .id(entity.getId()) + .nombre(entity.getNombre()) + .precio(entity.getPrecio()) + .marca(entity.getMarca()) + .categoria(entity.getCategoria()) + .stock(entity.getStock()) + .build(); } } @@ -29,15 +30,15 @@ public Articulo fromDto(ArticuloResponseDto dto) { if (dto == null) { return null; } else { - return Articulo.builder() - .id(dto.getId()) - .nombre(dto.getNombre()) - .precio(dto.getPrecio()) - .marca(dto.getMarca()) - .categoria(dto.getCategoria()) - .stock(dto.getStock()) - .build(); + return Articulo + .builder() + .id(dto.getId()) + .nombre(dto.getNombre()) + .precio(dto.getPrecio()) + .marca(dto.getMarca()) + .categoria(dto.getCategoria()) + .stock(dto.getStock()) + .build(); } } - } diff --git a/src/main/java/pe/edu/utp/converter/UsuarioConverter.java b/src/main/java/pe/edu/utp/converter/UsuarioConverter.java index 991f0be..0a524b9 100644 --- a/src/main/java/pe/edu/utp/converter/UsuarioConverter.java +++ b/src/main/java/pe/edu/utp/converter/UsuarioConverter.java @@ -1,6 +1,7 @@ package pe.edu.utp.converter; import org.springframework.stereotype.Component; + import pe.edu.utp.dto.UsuarioResponseDto; import pe.edu.utp.entity.Rol; import pe.edu.utp.entity.Usuario; @@ -12,12 +13,13 @@ public class UsuarioConverter extends AbstractConverter> handleValidationExceptions(MethodArgumentNotValidException ex, - WebRequest request) { - // Extrae los errores de validación y los mapea a tu estructura ApiError - List errors = ex.getBindingResult().getFieldErrors().stream() - .map(fieldError -> new ApiError( - fieldError.getCode() != null ? fieldError.getCode() : "VALIDATION_ERROR", - fieldError.getField(), - fieldError.getDefaultMessage())) - .collect(Collectors.toList()); + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationExceptions( + MethodArgumentNotValidException ex, + WebRequest request + ) { + // Extrae los errores de validación y los mapea a tu estructura ApiError + List errors = ex + .getBindingResult() + .getFieldErrors() + .stream() + .map(fieldError -> + new ApiError( + fieldError.getCode() != null ? fieldError.getCode() : "VALIDATION_ERROR", + fieldError.getField(), + fieldError.getDefaultMessage() + ) + ) + .collect(Collectors.toList()); - // Construye la ApiResponse de error para validación - ApiResponse apiResponse = ApiResponse.error( - HttpStatus.BAD_REQUEST, // HTTP Status 400 - "VALIDATION_ERROR", - "Validation failed for one or more fields.", - errors - ); - return apiResponse.toResponseEntity(); + // Construye la ApiResponse de error para validación + ApiResponse apiResponse = ApiResponse.error( + HttpStatus.BAD_REQUEST, // HTTP Status 400 + "VALIDATION_ERROR", + "Validation failed for one or more fields.", + errors + ); + return apiResponse.toResponseEntity(); + } + + @ExceptionHandler(NoDataFoundException.class) + public ResponseEntity> handleNoDataFoundException( + NoDataFoundException ex, + WebRequest request + ) { + String message = ex.getMessage(); + if (message == null || message.isEmpty()) { + message = "The requested resource could not be found."; } - @ExceptionHandler(NoDataFoundException.class) - public ResponseEntity> handleNoDataFoundException(NoDataFoundException ex, WebRequest request) { - String message = ex.getMessage(); - if (message == null || message.isEmpty()) { - message = "The requested resource could not be found."; - } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + Throwable exceptionToPrint = (ex.getCause() != null) ? ex.getCause() : ex; + exceptionToPrint.printStackTrace(pw); + String debugMessage = + "Request URI: " + + request.getDescription(false) + + "\n" + + "Exception Type: " + + ex.getClass().getName() + + "\n" + + "Exception Message: " + + ex.getMessage() + + "\n" + + ( + ex.getCause() != null + ? "Caused by: " + + ex.getCause().getClass().getName() + + " - " + + ex.getCause().getMessage() + + "\n" + : "" + ) + + "Stack Trace:\n" + + sw.toString(); - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - Throwable exceptionToPrint = (ex.getCause() != null) ? ex.getCause() : ex; - exceptionToPrint.printStackTrace(pw); - String debugMessage = "Request URI: " + request.getDescription(false) + "\n" + - "Exception Type: " + ex.getClass().getName() + "\n" + - "Exception Message: " + ex.getMessage() + "\n" + - (ex.getCause() != null ? "Caused by: " + ex.getCause().getClass().getName() + " - " + ex.getCause().getMessage() + "\n" : "") + - "Stack Trace:\n" + sw.toString(); + // Usar ApiResponse.error() + ApiResponse apiResponse = ApiResponse.error( + HttpStatus.NOT_FOUND, + "RESOURCE_NOT_FOUND", + message, + debugMessage + ); + return apiResponse.toResponseEntity(); + } - // Usar ApiResponse.error() - ApiResponse apiResponse = ApiResponse.error( - HttpStatus.NOT_FOUND, - "RESOURCE_NOT_FOUND", - message, - debugMessage - ); - return apiResponse.toResponseEntity(); + @ExceptionHandler(EmailAlreadyException.class) + public ResponseEntity> handleEmailAlreadyException( + EmailAlreadyException ex, + WebRequest request + ) { + String message = ex.getMessage(); + if (message == null || message.isEmpty()) { + message = "The email provided is already registered."; } - @ExceptionHandler(EmailAlreadyException.class) - public ResponseEntity> handleEmailAlreadyException(EmailAlreadyException ex, WebRequest request) { - String message = ex.getMessage(); - if (message == null || message.isEmpty()) { - message = "The email provided is already registered."; - } - - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - ex.printStackTrace(pw); - String debugMessage = "Request URI: " + request.getDescription(false) + "\n" + - "Exception Type: " + ex.getClass().getName() + "\n" + - "Exception Message: " + ex.getMessage() + "\n" + - "Stack Trace:\n" + sw.toString(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + String debugMessage = + "Request URI: " + + request.getDescription(false) + + "\n" + + "Exception Type: " + + ex.getClass().getName() + + "\n" + + "Exception Message: " + + ex.getMessage() + + "\n" + + "Stack Trace:\n" + + sw.toString(); - // Usar ApiResponse.error() - ApiResponse apiResponse = ApiResponse.error( - HttpStatus.CONFLICT, - "EMAIL_ALREADY_EXISTS", - message, - debugMessage - ); - return apiResponse.toResponseEntity(); - } + // Usar ApiResponse.error() + ApiResponse apiResponse = ApiResponse.error( + HttpStatus.CONFLICT, + "EMAIL_ALREADY_EXISTS", + message, + debugMessage + ); + return apiResponse.toResponseEntity(); + } - @ExceptionHandler(Exception.class) - public ResponseEntity> handleGeneralException(Exception ex, WebRequest request) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - ex.printStackTrace(pw); - String debugMessage = "Unexpected error: " + ex.getClass().getName() + " - " + ex.getMessage() - + "\nStack Trace: " + sw.toString(); + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGeneralException( + Exception ex, + WebRequest request + ) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + String debugMessage = + "Unexpected error: " + + ex.getClass().getName() + + " - " + + ex.getMessage() + + "\nStack Trace: " + + sw.toString(); - // Usar ApiResponse.error() - ApiResponse apiResponse = ApiResponse.error( - HttpStatus.INTERNAL_SERVER_ERROR, - "INTERNAL_SERVER_ERROR", - "An unexpected error occurred. Please check logs.", - debugMessage - ); - return apiResponse.toResponseEntity(); - } + // Usar ApiResponse.error() + ApiResponse apiResponse = ApiResponse.error( + HttpStatus.INTERNAL_SERVER_ERROR, + "INTERNAL_SERVER_ERROR", + "An unexpected error occurred. Please check logs.", + debugMessage + ); + return apiResponse.toResponseEntity(); + } } diff --git a/src/main/java/pe/edu/utp/exception/NoDataFoundException.java b/src/main/java/pe/edu/utp/exception/NoDataFoundException.java index 26c0cf2..925ab2d 100644 --- a/src/main/java/pe/edu/utp/exception/NoDataFoundException.java +++ b/src/main/java/pe/edu/utp/exception/NoDataFoundException.java @@ -13,5 +13,4 @@ public NoDataFoundException() { public NoDataFoundException(String message) { super(message); } - } diff --git a/src/main/java/pe/edu/utp/repository/ArticuloRepository.java b/src/main/java/pe/edu/utp/repository/ArticuloRepository.java index 09d41f7..4ceca81 100644 --- a/src/main/java/pe/edu/utp/repository/ArticuloRepository.java +++ b/src/main/java/pe/edu/utp/repository/ArticuloRepository.java @@ -1,6 +1,7 @@ package pe.edu.utp.repository; import java.util.List; + import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -11,19 +12,20 @@ @Repository public interface ArticuloRepository extends JpaRepository { - - @Query(""" + @Query( + """ SELECT a FROM Articulo a WHERE (:categoria IS NULL OR a.categoria = :categoria) AND (:marca IS NULL OR a.marca = :marca) AND (:precioMin IS NULL OR a.precio >= :precioMin) AND (:precioMax IS NULL OR a.precio <= :precioMax) - """) + """ + ) List findByCategoriaAndMarcaAndPrecioBetween( - @Param("categoria") String categoria, - @Param ("marca") String marca, - @Param("precioMin") Double precioMin, - @Param ("precioMax") Double precioMax, - Pageable pageable); - + @Param("categoria") String categoria, + @Param("marca") String marca, + @Param("precioMin") Double precioMin, + @Param("precioMax") Double precioMax, + Pageable pageable + ); } diff --git a/src/main/java/pe/edu/utp/repository/UsuarioRepository.java b/src/main/java/pe/edu/utp/repository/UsuarioRepository.java index e61b820..4cdda09 100644 --- a/src/main/java/pe/edu/utp/repository/UsuarioRepository.java +++ b/src/main/java/pe/edu/utp/repository/UsuarioRepository.java @@ -9,6 +9,5 @@ @Repository public interface UsuarioRepository extends JpaRepository { - Optional findByEmail(String email); } diff --git a/src/main/java/pe/edu/utp/security/CustomerUserDetailsService.java b/src/main/java/pe/edu/utp/security/CustomerUserDetailsService.java index 83bf2d3..388e101 100644 --- a/src/main/java/pe/edu/utp/security/CustomerUserDetailsService.java +++ b/src/main/java/pe/edu/utp/security/CustomerUserDetailsService.java @@ -1,11 +1,11 @@ package pe.edu.utp.security; +import lombok.RequiredArgsConstructor; 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; -import lombok.RequiredArgsConstructor; import pe.edu.utp.entity.Usuario; import pe.edu.utp.repository.UsuarioRepository; @@ -13,18 +13,21 @@ @RequiredArgsConstructor public class CustomerUserDetailsService implements UserDetailsService { - private final UsuarioRepository usuarioRepository; - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - // TODO Auto-generated method stub - Usuario usuario = usuarioRepository.findByEmail(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); + private final UsuarioRepository usuarioRepository; - return org.springframework.security.core.userdetails.User.builder() - .username(usuario.getEmail()) - .password(usuario.getPassword()) - .roles(usuario.getRol().name()).build(); - } + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // TODO Auto-generated method stub + Usuario usuario = usuarioRepository + .findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username) + ); + return org.springframework.security.core.userdetails.User + .builder() + .username(usuario.getEmail()) + .password(usuario.getPassword()) + .roles(usuario.getRol().name()) + .build(); + } } diff --git a/src/main/java/pe/edu/utp/security/JwtAuthenticationFilter.java b/src/main/java/pe/edu/utp/security/JwtAuthenticationFilter.java index fae3b23..0157766 100644 --- a/src/main/java/pe/edu/utp/security/JwtAuthenticationFilter.java +++ b/src/main/java/pe/edu/utp/security/JwtAuthenticationFilter.java @@ -1,21 +1,10 @@ package pe.edu.utp.security; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - import com.fasterxml.jackson.databind.ObjectMapper; - import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; @@ -24,79 +13,102 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; @Component @RequiredArgsConstructor @Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtService jwtService; private final CustomerUserDetailsService customerUserDetailsService; private final ObjectMapper objectMapper; @Override protected void doFilterInternal( - HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - - try{ - String jwt = jwtService.extractToken(request); - if(jwt == null ){ - log.info("JWT token is missing for request: {}", request.getRequestURI()); - filterChain.doFilter(request, response); // Pasa la solicitud si no hay JWT - return; // Importante: retornar después de pasar - } - - // Opcional: Si el usuario ya está autenticado, no procesar el JWT de nuevo - if (SecurityContextHolder.getContext().getAuthentication() != null && - SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) { - log.debug("User already authenticated in SecurityContext. Skipping JWT processing for {}.", request.getRequestURI()); - filterChain.doFilter(request, response); // Pasa la solicitud si ya está autenticado - return; - } + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + try { + String jwt = jwtService.extractToken(request); + if (jwt == null) { + log.info("JWT token is missing for request: {}", request.getRequestURI()); + filterChain.doFilter(request, response); // Pasa la solicitud si no hay JWT + return; // Importante: retornar después de pasar + } - Claims claims = jwtService.resolveClaims(request); // Esto puede lanzar ExpiredJwtException, etc. + // Opcional: Si el usuario ya está autenticado, no procesar el JWT de nuevo + if ( + SecurityContextHolder.getContext().getAuthentication() != null && + SecurityContextHolder.getContext().getAuthentication().isAuthenticated() + ) { + log.debug( + "User already authenticated in SecurityContext. Skipping JWT processing for {}.", + request.getRequestURI() + ); + filterChain.doFilter(request, response); // Pasa la solicitud si ya está autenticado + return; + } - if(claims != null && jwtService.validateClaims(claims)){ - String username = jwtService.extractUsername(jwt); - UserDetails userDetails = customerUserDetailsService.loadUserByUsername(username); + Claims claims = jwtService.resolveClaims(request); // Esto puede lanzar ExpiredJwtException, etc. - Authentication authentication = - new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - log.debug("User '{}' authenticated via JWT with roles: {}. Proceeding with filter chain.", username, userDetails.getAuthorities()); - } else { - // Si el token no es válido (ej. claims null o no validan), pero no lanzó una excepción, - // loguear y opcionalmente denegar o simplemente pasar sin autenticar. - log.warn("Invalid JWT token or claims for {}. Not authenticating.", request.getRequestURI()); - // Podrías poner un error 401/403 aquí si un token _debe_ ser válido para cualquier ruta no permitAll - // response.setStatus(HttpStatus.UNAUTHORIZED.value()); - // response.setContentType(MediaType.APPLICATION_JSON_VALUE); - // objectMapper.writeValue(response.getWriter(), Map.of("error", "Invalid JWT Token")); - // return; - } - - // ¡ESTA ES LA LÍNEA CLAVE PARA LAS SOLICITUDES CON TOKEN VÁLIDO! - filterChain.doFilter(request, response); + if (claims != null && jwtService.validateClaims(claims)) { + String username = jwtService.extractUsername(jwt); + UserDetails userDetails = customerUserDetailsService.loadUserByUsername(username); - } catch (ExpiredJwtException ex) { - log.error("JWT token expired for request {}: {}", request.getRequestURI(), ex.getMessage()); - Map errorDetails = new HashMap<>(); - errorDetails.put("error", "JWT Token expired"); - response.setStatus(HttpStatus.UNAUTHORIZED.value()); // Un 401 es más apropiado para token expirado - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - objectMapper.writeValue(response.getWriter(), errorDetails); - - } catch (Exception e) { // Captura cualquier otra excepción relacionada con el JWT (firma inválida, etc.) - log.error("Error processing JWT token for request {}: {}", request.getRequestURI(), e.getMessage()); - Map errorDetails = new HashMap<>(); - errorDetails.put("error", "Invalid JWT token"); - response.setStatus(HttpStatus.FORBIDDEN.value()); // O 401 Unauthorized - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - objectMapper.writeValue(response.getWriter(), errorDetails); - - } + Authentication authentication = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + SecurityContextHolder.getContext().setAuthentication(authentication); + log.debug( + "User '{}' authenticated via JWT with roles: {}. Proceeding with filter chain.", + username, + userDetails.getAuthorities() + ); + } else { + // Si el token no es válido (ej. claims null o no validan), pero no lanzó una excepción, + // loguear y opcionalmente denegar o simplemente pasar sin autenticar. + log.warn( + "Invalid JWT token or claims for {}. Not authenticating.", + request.getRequestURI() + ); + // Podrías poner un error 401/403 aquí si un token _debe_ ser válido para cualquier ruta no permitAll + // response.setStatus(HttpStatus.UNAUTHORIZED.value()); + // response.setContentType(MediaType.APPLICATION_JSON_VALUE); + // objectMapper.writeValue(response.getWriter(), Map.of("error", "Invalid JWT Token")); + // return; + } + // ¡ESTA ES LA LÍNEA CLAVE PARA LAS SOLICITUDES CON TOKEN VÁLIDO! + filterChain.doFilter(request, response); + } catch (ExpiredJwtException ex) { + log.error("JWT token expired for request {}: {}", request.getRequestURI(), ex.getMessage()); + Map errorDetails = new HashMap<>(); + errorDetails.put("error", "JWT Token expired"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); // Un 401 es más apropiado para token expirado + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + objectMapper.writeValue(response.getWriter(), errorDetails); + } catch (Exception e) { // Captura cualquier otra excepción relacionada con el JWT (firma inválida, etc.) + log.error( + "Error processing JWT token for request {}: {}", + request.getRequestURI(), + e.getMessage() + ); + Map errorDetails = new HashMap<>(); + errorDetails.put("error", "Invalid JWT token"); + response.setStatus(HttpStatus.FORBIDDEN.value()); // O 401 Unauthorized + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + objectMapper.writeValue(response.getWriter(), errorDetails); + } } } diff --git a/src/main/java/pe/edu/utp/security/JwtService.java b/src/main/java/pe/edu/utp/security/JwtService.java index bf39410..1d43152 100644 --- a/src/main/java/pe/edu/utp/security/JwtService.java +++ b/src/main/java/pe/edu/utp/security/JwtService.java @@ -5,9 +5,6 @@ import java.util.Map; import java.util.function.Function; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Service; - import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -15,6 +12,8 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor @@ -22,7 +21,7 @@ public class JwtService { private final PemReader pemReader; - private final long accessTokenExpirationTime = 1000L * 60 * 24*60; + private final long accessTokenExpirationTime = 1000L * 60 * 24 * 60; private static final String TOKEN_HEADER = "Authorization"; private static final String TOKEN_PREFIX = "Bearer "; @@ -36,11 +35,12 @@ public T extractClaim(String token, Function claimsResolver) { } private Claims extractAllClaims(String token) { - Claims claims = Jwts.parserBuilder() - .setSigningKey(pemReader.getPublicKey()) - .build() - .parseClaimsJws(token) - .getBody(); + Claims claims = Jwts + .parserBuilder() + .setSigningKey(pemReader.getPublicKey()) + .build() + .parseClaimsJws(token) + .getBody(); log.info("Claims: {}", claims); return claims; } @@ -79,13 +79,14 @@ public String generateToken(Map extraClaims, UserDetails userDet claims.put("roles", userDetails.getAuthorities().toString()); claims.putAll(extraClaims); // Agregar campos adicionales - return Jwts.builder() - .setHeaderParam("typ", "JWT") - .setClaims(claims) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + accessTokenExpirationTime)) - .signWith(pemReader.getPrivateKey(), SignatureAlgorithm.RS512) - .compact(); + return Jwts + .builder() + .setHeaderParam("typ", "JWT") + .setClaims(claims) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + accessTokenExpirationTime)) + .signWith(pemReader.getPrivateKey(), SignatureAlgorithm.RS512) + .compact(); } public boolean isTokenValid(String token, UserDetails userDetails) { diff --git a/src/main/java/pe/edu/utp/security/PemReader.java b/src/main/java/pe/edu/utp/security/PemReader.java index 40f0ba8..3121a20 100644 --- a/src/main/java/pe/edu/utp/security/PemReader.java +++ b/src/main/java/pe/edu/utp/security/PemReader.java @@ -1,6 +1,5 @@ package pe.edu.utp.security; - import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -9,19 +8,21 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; + import org.springframework.stereotype.Component; @Component public class PemReader { + private static final String PRIVATE_KEY = "JWT_RS512_PRIVATE_KEY"; private static final String PUBLIC_KEY = "JWT_RS512_PUBLIC_KEY"; public PrivateKey getPrivateKey() { - String privatePemKey = - System.getenv(PRIVATE_KEY) - .replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\s", ""); + String privatePemKey = System + .getenv(PRIVATE_KEY) + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); byte[] binaryEncoded = Base64.getDecoder().decode(privatePemKey); @@ -29,18 +30,17 @@ public PrivateKey getPrivateKey() { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(binaryEncoded); return keyFactory.generatePrivate(spec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new RuntimeException(e); } } public PublicKey getPublicKey() { - String publicPemKey = - System.getenv(PUBLIC_KEY) - .replace("-----BEGIN PUBLIC KEY-----", "") - .replace("-----END PUBLIC KEY-----", "") - .replaceAll("\\s", ""); + String publicPemKey = System + .getenv(PUBLIC_KEY) + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s", ""); byte[] binaryEncoded = Base64.getDecoder().decode(publicPemKey); @@ -48,7 +48,6 @@ public PublicKey getPublicKey() { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec spec = new X509EncodedKeySpec(binaryEncoded); return keyFactory.generatePublic(spec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { throw new RuntimeException(e); } diff --git a/src/main/java/pe/edu/utp/security/SecurityConfiguration.java b/src/main/java/pe/edu/utp/security/SecurityConfiguration.java index c12d635..2765bc9 100644 --- a/src/main/java/pe/edu/utp/security/SecurityConfiguration.java +++ b/src/main/java/pe/edu/utp/security/SecurityConfiguration.java @@ -1,5 +1,7 @@ package pe.edu.utp.security; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,9 +17,6 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -25,54 +24,56 @@ @Slf4j public class SecurityConfiguration { - private final JwtAuthenticationFilter jwtAuthenticationFilter; - - @Value("${spring.security.disabled:false}") - private boolean disabledSecurity; + private final JwtAuthenticationFilter jwtAuthenticationFilter; - /* - * Sprign security detecta un bean UserDetailsService y un PasswordEncoder, - * y automaticamente va a configurar una instancia de DaoAuthenticationProvider - * interno - * que utiliza el UserDetailsService y el PasswordEncoder. - * Luego crea una instancia de un ProviderManager, q registra al - * DaoAuthenticationProvider - * y lo devuelve como AuthenticationManager. - */ - @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) - throws Exception { - return authenticationConfiguration.getAuthenticationManager(); - } + @Value("${spring.security.disabled:false}") + private boolean disabledSecurity; - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + /* + * Sprign security detecta un bean UserDetailsService y un PasswordEncoder, + * y automaticamente va a configurar una instancia de DaoAuthenticationProvider + * interno + * que utiliza el UserDetailsService y el PasswordEncoder. + * Luego crea una instancia de un ProviderManager, q registra al + * DaoAuthenticationProvider + * y lo devuelve como AuthenticationManager. + */ + @Bean + public AuthenticationManager authenticationManager( + AuthenticationConfiguration authenticationConfiguration + ) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } - http.cors(cors -> cors.configure(http)); - http.csrf(AbstractHttpConfigurer::disable); - http.authorizeHttpRequests( - authorize -> authorize - .requestMatchers( - "/actuator/health", - "/usuarios-register", - "/usuarios/login", - "/signup/**", - "/v3/**", - "/doc/swagger-ui/**") - .permitAll() - .anyRequest() - .authenticated()); + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.cors(cors -> cors.configure(http)); + http.csrf(AbstractHttpConfigurer::disable); + http.authorizeHttpRequests(authorize -> + authorize + .requestMatchers( + "/actuator/health", + "/usuarios-register", + "/usuarios/login", + "/signup/**", + "/v3/**", + "/doc/swagger-ui/**" + ) + .permitAll() + .anyRequest() + .authenticated() + ); - http.sessionManagement( - sessionManagement -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + http.sessionManagement(sessionManagement -> + sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ); - http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - return http.build(); - } + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B); - } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B); + } } diff --git a/src/main/java/pe/edu/utp/service/ArticuloService.java b/src/main/java/pe/edu/utp/service/ArticuloService.java index 90af093..9788eeb 100644 --- a/src/main/java/pe/edu/utp/service/ArticuloService.java +++ b/src/main/java/pe/edu/utp/service/ArticuloService.java @@ -9,14 +9,18 @@ import pe.edu.utp.dto.ArticuloResponseDto; import pe.edu.utp.entity.Articulo; - public interface ArticuloService { - - List findAll(Pageable page); - List findByCategoriaAndMarcaAndPrecio(String categoria, String marca,Double precioMin, Double precioMax, Pageable pageable); - ArticuloResponseDto findById(Integer id); - Articulo save(ArticuloDto articulo); - Articulo update(ArticuloDto articulo, Integer id); - Articulo partialUpdate(Integer id, Map fields); - void delete(Integer id); + List findAll(Pageable page); + List findByCategoriaAndMarcaAndPrecio( + String categoria, + String marca, + Double precioMin, + Double precioMax, + Pageable pageable + ); + ArticuloResponseDto findById(Integer id); + Articulo save(ArticuloDto articulo); + Articulo update(ArticuloDto articulo, Integer id); + Articulo partialUpdate(Integer id, Map fields); + void delete(Integer id); } diff --git a/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java b/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java index 6ab2f6e..722039d 100644 --- a/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java +++ b/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java @@ -4,12 +4,12 @@ import java.util.List; import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.util.ReflectionUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import pe.edu.utp.converter.ArticuloConverter; import pe.edu.utp.converter.ArticuloResponseConverter; import pe.edu.utp.dto.ArticuloDto; @@ -23,67 +23,78 @@ @RequiredArgsConstructor public class ArticuloServiceImpl implements ArticuloService { - private final ArticuloRepository articuloRepository; - private final ArticuloConverter articuloConverter; - private final ArticuloResponseConverter articuloResponseConverter; - - @Override - public List findAll(Pageable page) { - var articulos = articuloRepository.findAll(page).toList(); - return articuloResponseConverter.fromEntity(articulos); - - } - - @Override - public List findByCategoriaAndMarcaAndPrecio(String categoria, String marca, Double precioMin, - Double precioMax, Pageable pageable) { - var result = articuloResponseConverter.fromEntity( - articuloRepository.findByCategoriaAndMarcaAndPrecioBetween(categoria, marca, precioMin, precioMax, - pageable)); - return result; - } - - @Override - public ArticuloResponseDto findById(Integer id) { - Articulo articuloDB = findByArticleId(id); - return articuloResponseConverter.fromEntity(articuloDB); - } - - @Override - public Articulo save(ArticuloDto articulo) { - var entity = articuloConverter.fromDto(articulo); - return articuloRepository.save(entity); - } - - @Override - public Articulo update(ArticuloDto articuloDto, Integer id) { - Articulo registro = findByArticleId(id); - registro.setNombre(articuloDto.getNombre()); - registro.setPrecio(articuloDto.getPrecio()); - return articuloRepository.save(registro); - } - - @Override - public void delete(Integer id) { - Articulo registro = findByArticleId(id); - articuloRepository.delete(registro); - - } - - private Articulo findByArticleId(Integer id) { - return articuloRepository.findById(id).orElseThrow( - () -> new NoDataFoundException("No existe un articulo con ese id: %d".formatted(id))); - } - - @Override - public Articulo partialUpdate(Integer id, Map fields) { - var articuloDb = findByArticleId(id); - fields.forEach((key, value)->{ - Field field = ReflectionUtils.findField(Articulo.class, key); - field.setAccessible(true); - ReflectionUtils.setField(field, articuloDb, value); - }); - return articuloRepository.save(articuloDb); - } - + private final ArticuloRepository articuloRepository; + private final ArticuloConverter articuloConverter; + private final ArticuloResponseConverter articuloResponseConverter; + + @Override + public List findAll(Pageable page) { + var articulos = articuloRepository.findAll(page).toList(); + return articuloResponseConverter.fromEntity(articulos); + } + + @Override + public List findByCategoriaAndMarcaAndPrecio( + String categoria, + String marca, + Double precioMin, + Double precioMax, + Pageable pageable + ) { + var result = articuloResponseConverter.fromEntity( + articuloRepository.findByCategoriaAndMarcaAndPrecioBetween( + categoria, + marca, + precioMin, + precioMax, + pageable + ) + ); + return result; + } + + @Override + public ArticuloResponseDto findById(Integer id) { + Articulo articuloDB = findByArticleId(id); + return articuloResponseConverter.fromEntity(articuloDB); + } + + @Override + public Articulo save(ArticuloDto articulo) { + var entity = articuloConverter.fromDto(articulo); + return articuloRepository.save(entity); + } + + @Override + public Articulo update(ArticuloDto articuloDto, Integer id) { + Articulo registro = findByArticleId(id); + registro.setNombre(articuloDto.getNombre()); + registro.setPrecio(articuloDto.getPrecio()); + return articuloRepository.save(registro); + } + + @Override + public void delete(Integer id) { + Articulo registro = findByArticleId(id); + articuloRepository.delete(registro); + } + + private Articulo findByArticleId(Integer id) { + return articuloRepository + .findById(id) + .orElseThrow(() -> + new NoDataFoundException("No existe un articulo con ese id: %d".formatted(id)) + ); + } + + @Override + public Articulo partialUpdate(Integer id, Map fields) { + var articuloDb = findByArticleId(id); + fields.forEach((key, value) -> { + Field field = ReflectionUtils.findField(Articulo.class, key); + field.setAccessible(true); + ReflectionUtils.setField(field, articuloDb, value); + }); + return articuloRepository.save(articuloDb); + } } diff --git a/src/main/java/pe/edu/utp/service/UsuarioService.java b/src/main/java/pe/edu/utp/service/UsuarioService.java index 1ead8c5..5aff57c 100644 --- a/src/main/java/pe/edu/utp/service/UsuarioService.java +++ b/src/main/java/pe/edu/utp/service/UsuarioService.java @@ -1,19 +1,24 @@ package pe.edu.utp.service; +import java.util.List; + import org.springframework.data.domain.Pageable; + import pe.edu.utp.dto.LoginRequestDto; import pe.edu.utp.dto.LoginResponseDto; import pe.edu.utp.dto.UsuarioRequestDto; import pe.edu.utp.entity.Usuario; -import java.util.List; - public interface UsuarioService { - public List findAll(Pageable page) throws Exception; - public Usuario findById(int id); - public Usuario update(UsuarioRequestDto usuario, Integer id); - public Usuario save(UsuarioRequestDto usuario); - public void delete(int id); - public LoginResponseDto login(LoginRequestDto loginRequestDto); + public List findAll(Pageable page) throws Exception; + + public Usuario findById(int id); + + public Usuario update(UsuarioRequestDto usuario, Integer id); + + public Usuario save(UsuarioRequestDto usuario); + + public void delete(int id); + public LoginResponseDto login(LoginRequestDto loginRequestDto); } diff --git a/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java b/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java index 7e2d52e..247a15c 100644 --- a/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java +++ b/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java @@ -2,15 +2,13 @@ import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import lombok.RequiredArgsConstructor; import pe.edu.utp.converter.UsuarioConverter; import pe.edu.utp.dto.LoginRequestDto; import pe.edu.utp.dto.LoginResponseDto; @@ -22,77 +20,81 @@ import pe.edu.utp.repository.UsuarioRepository; import pe.edu.utp.security.JwtService; - @Service @RequiredArgsConstructor public class UsuarioServiceImpl implements UsuarioService { - private final UsuarioRepository usuarioRepository; - - private final PasswordEncoder encoder; - - private final AuthenticationManager authenticationManager; - - private final JwtService jwtService; - - private final UsuarioConverter usuarioConverter; - - @Override - public List findAll(Pageable page) throws Exception { - return usuarioRepository.findAll(page).toList(); - } - - @Override - public Usuario findById(int id) { - return usuarioRepository.findById(id).orElseThrow( - () -> new NoDataFoundException("No existe el usuario con id: %d".formatted(id))); - } - - @Override - public Usuario update(UsuarioRequestDto usuario, Integer id) { - - usuarioRepository.findByEmail(usuario.getEmail()).orElseThrow( - () -> new EmailAlreadyException("El email: %s ya esta registrado".formatted(usuario.getEmail()))); - - Usuario registro = findById(id); - registro.setRol(Rol.valueOf(usuario.getRol())); - registro.setEmail(usuario.getEmail()); - registro.setPassword(encoder.encode(usuario.getPassword())); - usuarioRepository.save(registro); - return registro; - - } - - @Override - public Usuario save(UsuarioRequestDto usuario) { - - var emailRegister = usuarioRepository.findByEmail(usuario.getEmail()); - if(emailRegister.isPresent()){ - throw new EmailAlreadyException("El email: %s ya esta registrado".formatted(usuario.getEmail())); - } - - var user = new Usuario(); - user.setPassword(encoder.encode(usuario.getPassword())); - user.setEmail(usuario.getEmail()); - user.setRol(Rol.valueOf(usuario.getRol())); - var result = usuarioRepository.save(user); - return result; - } - - @Override - public void delete(int id) { - var userDb = findById(id); - usuarioRepository.delete(userDb); - } - - - @Override - public LoginResponseDto login(LoginRequestDto loginRequestDto) { - authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(loginRequestDto.getEmail(), loginRequestDto.getPassword())); - var usuario = usuarioRepository.findByEmail(loginRequestDto.getEmail()).orElseThrow(); - var jwtToken = jwtService.generateToken(usuario); - return new LoginResponseDto(usuarioConverter.fromEntity(usuario), jwtToken); + private final UsuarioRepository usuarioRepository; + + private final PasswordEncoder encoder; + + private final AuthenticationManager authenticationManager; + + private final JwtService jwtService; + + private final UsuarioConverter usuarioConverter; + + @Override + public List findAll(Pageable page) throws Exception { + return usuarioRepository.findAll(page).toList(); + } + + @Override + public Usuario findById(int id) { + return usuarioRepository + .findById(id) + .orElseThrow(() -> new NoDataFoundException("No existe el usuario con id: %d".formatted(id))); + } + + @Override + public Usuario update(UsuarioRequestDto usuario, Integer id) { + usuarioRepository + .findByEmail(usuario.getEmail()) + .orElseThrow(() -> + new EmailAlreadyException("El email: %s ya esta registrado".formatted(usuario.getEmail())) + ); + + Usuario registro = findById(id); + registro.setRol(Rol.valueOf(usuario.getRol())); + registro.setEmail(usuario.getEmail()); + registro.setPassword(encoder.encode(usuario.getPassword())); + usuarioRepository.save(registro); + return registro; + } + + @Override + public Usuario save(UsuarioRequestDto usuario) { + var emailRegister = usuarioRepository.findByEmail(usuario.getEmail()); + if (emailRegister.isPresent()) { + throw new EmailAlreadyException( + "El email: %s ya esta registrado".formatted(usuario.getEmail()) + ); } + var user = new Usuario(); + user.setPassword(encoder.encode(usuario.getPassword())); + user.setEmail(usuario.getEmail()); + user.setRol(Rol.valueOf(usuario.getRol())); + var result = usuarioRepository.save(user); + return result; + } + + @Override + public void delete(int id) { + var userDb = findById(id); + usuarioRepository.delete(userDb); + } + + @Override + public LoginResponseDto login(LoginRequestDto loginRequestDto) { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + loginRequestDto.getEmail(), + loginRequestDto.getPassword() + ) + ); + var usuario = usuarioRepository.findByEmail(loginRequestDto.getEmail()).orElseThrow(); + var jwtToken = jwtService.generateToken(usuario); + return new LoginResponseDto(usuarioConverter.fromEntity(usuario), jwtToken); + } } diff --git a/src/main/java/pe/edu/utp/util/ApiError.java b/src/main/java/pe/edu/utp/util/ApiError.java index b647005..d70d201 100644 --- a/src/main/java/pe/edu/utp/util/ApiError.java +++ b/src/main/java/pe/edu/utp/util/ApiError.java @@ -10,7 +10,8 @@ @NoArgsConstructor @AllArgsConstructor public class ApiError { - private String code; - private String field; - private String message; + + private String code; + private String field; + private String message; } diff --git a/src/main/java/pe/edu/utp/util/ApiResponse.java b/src/main/java/pe/edu/utp/util/ApiResponse.java index d778454..0692c96 100644 --- a/src/main/java/pe/edu/utp/util/ApiResponse.java +++ b/src/main/java/pe/edu/utp/util/ApiResponse.java @@ -3,19 +3,19 @@ import java.time.LocalDateTime; import java.util.List; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class ApiResponse { + private int status; private String code; private String message; @@ -23,50 +23,94 @@ public class ApiResponse { private T data; private List errors; - public static ApiResponse ok(String message, T data) { - return new ApiResponse<>(HttpStatus.OK.value(), "SUCCESS", message, LocalDateTime.now(), data, null); - } + public static ApiResponse ok(String message, T data) { + return new ApiResponse<>( + HttpStatus.OK.value(), + "SUCCESS", + message, + LocalDateTime.now(), + data, + null + ); + } - public static ApiResponse ok(T data) { - return ok("Operation successful", data); // Mensaje por defecto - } + public static ApiResponse ok(T data) { + return ok("Operation successful", data); // Mensaje por defecto + } - public static ApiResponse ok(String message) { - return ok(message, null); // Mensaje sin datos - } + public static ApiResponse ok(String message) { + return ok(message, null); // Mensaje sin datos + } - public static ApiResponse created(String message, T data) { - return new ApiResponse<>(HttpStatus.CREATED.value(), "SUCCESS", message, LocalDateTime.now(), data, null); - } + public static ApiResponse created(String message, T data) { + return new ApiResponse<>( + HttpStatus.CREATED.value(), + "SUCCESS", + message, + LocalDateTime.now(), + data, + null + ); + } - public static ApiResponse accepted(String message) { - return new ApiResponse<>(HttpStatus.ACCEPTED.value(), "SUCCESS", message, LocalDateTime.now(), null, null); - } + public static ApiResponse accepted(String message) { + return new ApiResponse<>( + HttpStatus.ACCEPTED.value(), + "SUCCESS", + message, + LocalDateTime.now(), + null, + null + ); + } - public static ApiResponse noContent() { - return new ApiResponse<>(HttpStatus.NO_CONTENT.value(), "SUCCESS", "No content", LocalDateTime.now(), null, null); - } + public static ApiResponse noContent() { + return new ApiResponse<>( + HttpStatus.NO_CONTENT.value(), + "SUCCESS", + "No content", + LocalDateTime.now(), + null, + null + ); + } - // --- Métodos de Ayuda para Respuestas de ERROR (como ya tenías) --- + // --- Métodos de Ayuda para Respuestas de ERROR (como ya tenías) --- - // Constructor/Método para errores generales (ej. manejado por GlobalExceptionHandler) - public static ApiResponse error(HttpStatus httpStatus, String code, String message, String debugMessage) { - // En producción, probablemente no incluyas debugMessage directamente aquí - return new ApiResponse<>(httpStatus.value(), code, message + (debugMessage != null ? " [Debug: " + debugMessage + "]" : ""), LocalDateTime.now(), null, null); - } + // Constructor/Método para errores generales (ej. manejado por GlobalExceptionHandler) + public static ApiResponse error( + HttpStatus httpStatus, + String code, + String message, + String debugMessage + ) { + // En producción, probablemente no incluyas debugMessage directamente aquí + return new ApiResponse<>( + httpStatus.value(), + code, + message + (debugMessage != null ? " [Debug: " + debugMessage + "]" : ""), + LocalDateTime.now(), + null, + null + ); + } - // Constructor/Método para errores con lista de ApiError (ej. validación) - public static ApiResponse error(HttpStatus httpStatus, String code, String message, List errors) { - return new ApiResponse<>(httpStatus.value(), code, message, LocalDateTime.now(), null, errors); - } + // Constructor/Método para errores con lista de ApiError (ej. validación) + public static ApiResponse error( + HttpStatus httpStatus, + String code, + String message, + List errors + ) { + return new ApiResponse<>(httpStatus.value(), code, message, LocalDateTime.now(), null, errors); + } - // Método de ayuda para construir ResponseEntity - public ResponseEntity> toResponseEntity() { - // Si el status es NO_CONTENT, el body debe ser null en ResponseEntity - if (this.status == HttpStatus.NO_CONTENT.value()) { - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - } - return new ResponseEntity<>(this, HttpStatus.valueOf(this.status)); + // Método de ayuda para construir ResponseEntity + public ResponseEntity> toResponseEntity() { + // Si el status es NO_CONTENT, el body debe ser null en ResponseEntity + if (this.status == HttpStatus.NO_CONTENT.value()) { + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } - + return new ResponseEntity<>(this, HttpStatus.valueOf(this.status)); + } } diff --git a/src/test/java/pe/edu/utp/ArticulosapiApplicationTests.java b/src/test/java/pe/edu/utp/ArticulosapiApplicationTests.java index cd8d2a0..499a04d 100644 --- a/src/test/java/pe/edu/utp/ArticulosapiApplicationTests.java +++ b/src/test/java/pe/edu/utp/ArticulosapiApplicationTests.java @@ -1,7 +1,5 @@ package pe.edu.utp; - - import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; @@ -9,11 +7,11 @@ @SpringBootTest class ArticulosapiApplicationTests { - @Value("${spring.datasource.url}") - private String dataSourceUrl; - @Test - void contextLoads() { - System.out.println("DataSource URL: " + dataSourceUrl); - } + @Value("${spring.datasource.url}") + private String dataSourceUrl; + @Test + void contextLoads() { + System.out.println("DataSource URL: " + dataSourceUrl); + } } diff --git a/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java b/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java index f2bede3..ee2497f 100644 --- a/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java +++ b/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java @@ -1,28 +1,20 @@ package pe.edu.utp.service; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; + import pe.edu.utp.entity.Rol; import pe.edu.utp.entity.Usuario; import pe.edu.utp.repository.UsuarioRepository; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - public class UsuarioServiceTest { - @Mock private UsuarioRepository usuarioRepository; + @Mock + private UsuarioRepository usuarioRepository; - @InjectMocks private UsuarioService usuarioService; + @InjectMocks + private UsuarioService usuarioService; private Usuario usuario; @@ -30,12 +22,12 @@ public class UsuarioServiceTest { void Setup() { // Initialize mocks and any required setup here usuario = - Usuario.builder() - .email("luis_18@gmail.com") - .password("securePassword123") - .activo(true) - .rol(Rol.ADMIN) - .build(); + Usuario + .builder() + .email("luis_18@gmail.com") + .password("securePassword123") + .activo(true) + .rol(Rol.ADMIN) + .build(); } - }