diff --git a/src/main/java/pe/edu/utp/controller/ArticuloController.java b/src/main/java/pe/edu/utp/controller/ArticuloController.java index 1b5fae7..98edd63 100644 --- a/src/main/java/pe/edu/utp/controller/ArticuloController.java +++ b/src/main/java/pe/edu/utp/controller/ArticuloController.java @@ -20,9 +20,11 @@ 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; import pe.edu.utp.exception.NoDataFoundException; import pe.edu.utp.service.ArticuloService; +import pe.edu.utp.util.ApiResponse; @RestController @RequestMapping("/articulos") @@ -33,36 +35,36 @@ public class ArticuloController { private final ArticuloService articuloService; @GetMapping - public ResponseEntity> getAll( + public ResponseEntity>> getAll( - @RequestParam(value = "marca", required = false, defaultValue = "") String marca, - @RequestParam(value = "categoria", required = false, defaultValue = "") String categoria, - @RequestParam(value = "precioMin", required = false, defaultValue = "0") Double precioMin, - @RequestParam(value = "precioMax", required = false, defaultValue = "0") Double precioMax, + @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.findAll(pageable); - } else { + List articulos; + if (marca !=null || categoria != null || precioMin != null || precioMax != null) { articulos = articuloService.findByCategoriaAndMarcaAndPrecio(categoria, marca, precioMin, precioMax, pageable); + } else { + articulos = articuloService.findAll(pageable); } - return ResponseEntity.ok(articulos); + return ApiResponse.ok(articulos).toResponseEntity(); } @GetMapping(value = "/{id}") - public ResponseEntity findById(@PathVariable("id") int id) { + public ResponseEntity> findById(@PathVariable("id") int id) { log.info("Obteniendo articulo con ID: {}", id); var response = articuloService.findById(id); - return ResponseEntity.ok(response); + return ApiResponse.ok(response).toResponseEntity(); } @PostMapping - public ResponseEntity create(@Valid @RequestBody ArticuloDto articuloDto) { + public ResponseEntity> create(@Valid @RequestBody ArticuloDto articuloDto) { Articulo registro = articuloService.save(articuloDto); - return ResponseEntity.status(HttpStatus.CREATED).body(registro); + return ApiResponse.created("Articulo creado exitosamente", registro).toResponseEntity(); } @PutMapping(value = "/{id}") diff --git a/src/main/java/pe/edu/utp/controller/UsuarioController.java b/src/main/java/pe/edu/utp/controller/UsuarioController.java index ed26285..ea46709 100644 --- a/src/main/java/pe/edu/utp/controller/UsuarioController.java +++ b/src/main/java/pe/edu/utp/controller/UsuarioController.java @@ -23,8 +23,7 @@ import pe.edu.utp.dto.UsuarioResponseDto; import pe.edu.utp.entity.Usuario; import pe.edu.utp.service.UsuarioService; -import pe.edu.utp.util.ConstantesHelpers; -import pe.edu.utp.util.WrapperResponse; +import pe.edu.utp.util.ApiResponse; @RestController @RequiredArgsConstructor @@ -35,7 +34,7 @@ public class UsuarioController { @GetMapping("/usuarios") - public ResponseEntity> findAll( + 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 { @@ -43,38 +42,36 @@ public ResponseEntity> findAll( Pageable pagina = PageRequest.of(pageNumber, pageSize); List registros = usuarioService.findAll(pagina); List registrosDTO = converter.fromEntity(registros); - return new WrapperResponse(true, "success", registrosDTO).createResponse(HttpStatus.OK); + return ApiResponse.ok("Users retrieved successfully", registrosDTO).toResponseEntity(); } @PostMapping("/usuarios-register") - public ResponseEntity create(@RequestBody UsuarioRequestDto usuario) { - Usuario registro = usuarioService.save(converter.registro(usuario)); - return new WrapperResponse(true, ConstantesHelpers.MESSAGE_SUCCESS, converter.fromEntity(registro)) - .createResponse(HttpStatus.CREATED); + public ResponseEntity> create(@RequestBody UsuarioRequestDto usuario) { + Usuario registro = usuarioService.save(usuario); + return ApiResponse.created("User created successfully", converter.fromEntity(registro)).toResponseEntity(); } @PutMapping(value = "/usuarios/{id}") - public ResponseEntity update( + public ResponseEntity> update( @PathVariable("id") int id, @RequestBody UsuarioRequestDto usuario) { - Usuario registro = usuarioService.update(converter.registro(usuario)); + Usuario registro = usuarioService.update(usuario, id); if (registro == null) { return ResponseEntity.notFound().build(); } - return new WrapperResponse(true, ConstantesHelpers.MESSAGE_SUCCESS, converter.fromEntity(registro)) - .createResponse(HttpStatus.OK); + return ApiResponse.ok("User updated successfully", converter.fromEntity(registro)).toResponseEntity(); } @DeleteMapping(value = "/usuarios/{id}") - public ResponseEntity delete(@PathVariable("id") int id) { + public ResponseEntity> delete(@PathVariable("id") int id) { usuarioService.delete(id); - return new WrapperResponse(true, ConstantesHelpers.MESSAGE_SUCCESS, null).createResponse(HttpStatus.OK); + return ApiResponse.noContent().toResponseEntity(); } @PostMapping(value = "/usuarios/login") - public ResponseEntity> login( + public ResponseEntity> login( @RequestBody LoginRequestDto request) { LoginResponseDto response = usuarioService.login(request); - return new WrapperResponse<>(true, ConstantesHelpers.MESSAGE_SUCCESS, response).createResponse(HttpStatus.OK); + 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 0ae7b92..96479ce 100644 --- a/src/main/java/pe/edu/utp/converter/ArticuloConverter.java +++ b/src/main/java/pe/edu/utp/converter/ArticuloConverter.java @@ -14,7 +14,6 @@ public ArticuloDto fromEntity(Articulo entity) { return null; } else { return ArticuloDto.builder() - .id(entity.getId()) .nombre(entity.getNombre()) .precio(entity.getPrecio()) .marca(entity.getMarca()) @@ -30,7 +29,6 @@ public Articulo fromDto(ArticuloDto dto) { return null; } else { return Articulo.builder() - .id(dto.getId()) .nombre(dto.getNombre()) .precio(dto.getPrecio()) .marca(dto.getMarca()) diff --git a/src/main/java/pe/edu/utp/converter/ArticuloResponseConverter.java b/src/main/java/pe/edu/utp/converter/ArticuloResponseConverter.java new file mode 100644 index 0000000..5b8ada1 --- /dev/null +++ b/src/main/java/pe/edu/utp/converter/ArticuloResponseConverter.java @@ -0,0 +1,43 @@ +package pe.edu.utp.converter; + +import org.springframework.stereotype.Component; + +import pe.edu.utp.dto.ArticuloResponseDto; +import pe.edu.utp.entity.Articulo; + +@Component +public class ArticuloResponseConverter extends AbstractConverter { + + @Override + 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(); + } + } + + @Override + 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(); + } + } + +} diff --git a/src/main/java/pe/edu/utp/converter/UsuarioConverter.java b/src/main/java/pe/edu/utp/converter/UsuarioConverter.java index e28d527..991f0be 100644 --- a/src/main/java/pe/edu/utp/converter/UsuarioConverter.java +++ b/src/main/java/pe/edu/utp/converter/UsuarioConverter.java @@ -1,7 +1,6 @@ package pe.edu.utp.converter; import org.springframework.stereotype.Component; -import pe.edu.utp.dto.UsuarioRequestDto; import pe.edu.utp.dto.UsuarioResponseDto; import pe.edu.utp.entity.Rol; import pe.edu.utp.entity.Usuario; @@ -36,9 +35,4 @@ public Usuario fromDto(UsuarioResponseDto dto) { } } - public Usuario registro(UsuarioRequestDto dto) { - if (dto == null) return null; - Rol rol = Rol.valueOf(dto.getRol().toUpperCase()); - return Usuario.builder().email(dto.getEmail()).password(dto.getPassword()).rol(rol).build(); - } } diff --git a/src/main/java/pe/edu/utp/dto/ArticuloDto.java b/src/main/java/pe/edu/utp/dto/ArticuloDto.java index ac2545e..9bcd920 100644 --- a/src/main/java/pe/edu/utp/dto/ArticuloDto.java +++ b/src/main/java/pe/edu/utp/dto/ArticuloDto.java @@ -14,9 +14,7 @@ @Getter @AllArgsConstructor public class ArticuloDto { - - private int id; - + @NotBlank(message = "El campo nombre no puede estar vacio") private String nombre; diff --git a/src/main/java/pe/edu/utp/dto/ArticuloResponseDto.java b/src/main/java/pe/edu/utp/dto/ArticuloResponseDto.java new file mode 100644 index 0000000..d6e0099 --- /dev/null +++ b/src/main/java/pe/edu/utp/dto/ArticuloResponseDto.java @@ -0,0 +1,23 @@ +package pe.edu.utp.dto; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Builder +@NoArgsConstructor +@Setter +@Getter +@AllArgsConstructor +public class ArticuloResponseDto { + + private Integer id; + private String nombre; + private String marca; + private String categoria; + private Double precio; + private int stock; +} diff --git a/src/main/java/pe/edu/utp/dto/RefreshTokenResponseDto.java b/src/main/java/pe/edu/utp/dto/RefreshTokenResponseDto.java deleted file mode 100644 index 416d806..0000000 --- a/src/main/java/pe/edu/utp/dto/RefreshTokenResponseDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package pe.edu.utp.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; - -@Setter -@Getter -@RequiredArgsConstructor -@AllArgsConstructor -public class RefreshTokenResponseDto { - private String token; - private String refreshToken; - private String name; -} diff --git a/src/main/java/pe/edu/utp/exception/EmailAlreadyException.java b/src/main/java/pe/edu/utp/exception/EmailAlreadyException.java new file mode 100644 index 0000000..cd40200 --- /dev/null +++ b/src/main/java/pe/edu/utp/exception/EmailAlreadyException.java @@ -0,0 +1,17 @@ +package pe.edu.utp.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.CONFLICT) +public class EmailAlreadyException extends RuntimeException { + + public EmailAlreadyException() { + super(); + } + + public EmailAlreadyException(String message) { + super(message); + } + +} diff --git a/src/main/java/pe/edu/utp/exception/GeneralServiceException.java b/src/main/java/pe/edu/utp/exception/GeneralServiceException.java deleted file mode 100644 index 8f53dea..0000000 --- a/src/main/java/pe/edu/utp/exception/GeneralServiceException.java +++ /dev/null @@ -1,28 +0,0 @@ -package pe.edu.utp.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) -public class GeneralServiceException extends RuntimeException { - public GeneralServiceException() { - super(); - } - - public GeneralServiceException( - String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - - public GeneralServiceException(String message, Throwable cause) { - super(message, cause); - } - - public GeneralServiceException(String message) { - super(message); - } - - public GeneralServiceException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/pe/edu/utp/exception/GlobalExceptionHandler.java b/src/main/java/pe/edu/utp/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..211864d --- /dev/null +++ b/src/main/java/pe/edu/utp/exception/GlobalExceptionHandler.java @@ -0,0 +1,115 @@ +package pe.edu.utp.exception; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +import pe.edu.utp.util.ApiError; +import pe.edu.utp.util.ApiResponse; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @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( + // Puedes ajustar el 'code' si quieres algo más específico (ej. + // "REQUIRED_FIELD", "INVALID_EMAIL_FORMAT") + // fieldError.getCode() a menudo es el nombre de la anotación de validación (ej. + // "NotBlank", "NotNull") + 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", // Código de error de negocio + "Validation failed for one or more fields.", // Mensaje general + errors // Lista de errores detallados + ); + 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."; + } + + 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(); + } + + @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(); + + // 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(); + + // 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 f19e459..26c0cf2 100644 --- a/src/main/java/pe/edu/utp/exception/NoDataFoundException.java +++ b/src/main/java/pe/edu/utp/exception/NoDataFoundException.java @@ -3,31 +3,15 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; -@ResponseStatus(code = HttpStatus.NO_CONTENT) +@ResponseStatus(code = HttpStatus.NOT_FOUND) public class NoDataFoundException extends RuntimeException { + public NoDataFoundException() { super(); - // TODO Auto-generated constructor stub - } - - public NoDataFoundException( - String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub - } - - public NoDataFoundException(String message, Throwable cause) { - super(message, cause); - // TODO Auto-generated constructor stub } public NoDataFoundException(String message) { super(message); - // TODO Auto-generated constructor stub } - public NoDataFoundException(Throwable cause) { - super(cause); - // TODO Auto-generated constructor stub - } } diff --git a/src/main/java/pe/edu/utp/exception/ValidateServiceException.java b/src/main/java/pe/edu/utp/exception/ValidateServiceException.java deleted file mode 100644 index 84169ca..0000000 --- a/src/main/java/pe/edu/utp/exception/ValidateServiceException.java +++ /dev/null @@ -1,33 +0,0 @@ -package pe.edu.utp.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(code = HttpStatus.BAD_REQUEST) -public class ValidateServiceException extends RuntimeException { - public ValidateServiceException() { - super(); - // TODO Auto-generated constructor stub - } - - public ValidateServiceException( - String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - // TODO Auto-generated constructor stub - } - - public ValidateServiceException(String message, Throwable cause) { - super(message, cause); - // TODO Auto-generated constructor stub - } - - public ValidateServiceException(String message) { - super(message); - // TODO Auto-generated constructor stub - } - - public ValidateServiceException(Throwable cause) { - super(cause); - // TODO Auto-generated constructor stub - } -} diff --git a/src/main/java/pe/edu/utp/repository/ArticuloRepository.java b/src/main/java/pe/edu/utp/repository/ArticuloRepository.java index 1aac5bd..09d41f7 100644 --- a/src/main/java/pe/edu/utp/repository/ArticuloRepository.java +++ b/src/main/java/pe/edu/utp/repository/ArticuloRepository.java @@ -3,6 +3,8 @@ import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import pe.edu.utp.entity.Articulo; @@ -10,6 +12,18 @@ @Repository public interface ArticuloRepository extends JpaRepository { - List findByCategoriaAndMarcaAndPrecioBetween(String categoria, String marca,Double precioMin,Double precioMax,Pageable pageable); + @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); } diff --git a/src/main/java/pe/edu/utp/repository/UsuarioRepository.java b/src/main/java/pe/edu/utp/repository/UsuarioRepository.java index 71236fe..e61b820 100644 --- a/src/main/java/pe/edu/utp/repository/UsuarioRepository.java +++ b/src/main/java/pe/edu/utp/repository/UsuarioRepository.java @@ -1,15 +1,14 @@ package pe.edu.utp.repository; -import org.springframework.data.domain.Pageable; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; + import pe.edu.utp.entity.Usuario; -import java.util.List; -import java.util.Optional; @Repository public interface UsuarioRepository extends JpaRepository { - List findByEmailContaining(String email, Pageable page); Optional findByEmail(String email); } diff --git a/src/main/java/pe/edu/utp/security/JwtService.java b/src/main/java/pe/edu/utp/security/JwtService.java index 82b3035..bf39410 100644 --- a/src/main/java/pe/edu/utp/security/JwtService.java +++ b/src/main/java/pe/edu/utp/security/JwtService.java @@ -22,7 +22,7 @@ public class JwtService { private final PemReader pemReader; - private final long accessTokenExpirationTime = 1000L * 60 * 24; + private final long accessTokenExpirationTime = 1000L * 60 * 24*60; private static final String TOKEN_HEADER = "Authorization"; private static final String TOKEN_PREFIX = "Bearer "; diff --git a/src/main/java/pe/edu/utp/service/ArticuloService.java b/src/main/java/pe/edu/utp/service/ArticuloService.java index b452a02..e4064e0 100644 --- a/src/main/java/pe/edu/utp/service/ArticuloService.java +++ b/src/main/java/pe/edu/utp/service/ArticuloService.java @@ -4,18 +4,17 @@ import org.springframework.data.domain.Pageable; -import pe.edu.utp.exception.NoDataFoundException; import pe.edu.utp.dto.ArticuloDto; +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); - ArticuloDto findById(int id); + List findAll(Pageable page); + List findByCategoriaAndMarcaAndPrecio(String categoria, String marca,Double precioMin, Double precioMax, Pageable pageable); + ArticuloResponseDto findById(int id); Articulo save(ArticuloDto articulo); Articulo update(ArticuloDto articulo, int id); - void delete(int id)throws NoDataFoundException; + void delete(int id); } diff --git a/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java b/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java index dfbbec6..f57d360 100644 --- a/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java +++ b/src/main/java/pe/edu/utp/service/ArticuloServiceImpl.java @@ -4,12 +4,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; 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; +import pe.edu.utp.dto.ArticuloResponseDto; import pe.edu.utp.entity.Articulo; import pe.edu.utp.exception.NoDataFoundException; import pe.edu.utp.repository.ArticuloRepository; @@ -21,40 +22,37 @@ public class ArticuloServiceImpl implements ArticuloService { private final ArticuloRepository articuloRepository; private final ArticuloConverter articuloConverter; + private final ArticuloResponseConverter articuloResponseConverter; @Override - public List findAll(Pageable page) { + public List findAll(Pageable page) { var articulos = articuloRepository.findAll(page).toList(); - return articuloConverter.fromEntity(articulos); + return articuloResponseConverter.fromEntity(articulos); } @Override - @Transactional(readOnly = true) - public List findByCategoriaAndMarcaAndPrecio(String categoria, String marca, Double precioMin, + public List findByCategoriaAndMarcaAndPrecio(String categoria, String marca, Double precioMin, Double precioMax, Pageable pageable) { - var result = articuloConverter.fromEntity( + var result = articuloResponseConverter.fromEntity( articuloRepository.findByCategoriaAndMarcaAndPrecioBetween(categoria, marca, precioMin, precioMax, pageable)); return result; } @Override - @Transactional(readOnly = true) - public ArticuloDto findById(int id) { + public ArticuloResponseDto findById(int id) { Articulo articuloDB = findByArticleId(id); - return articuloConverter.fromEntity(articuloDB); + return articuloResponseConverter.fromEntity(articuloDB); } @Override - @Transactional public Articulo save(ArticuloDto articulo) { var entity = articuloConverter.fromDto(articulo); return articuloRepository.save(entity); } @Override - @Transactional public Articulo update(ArticuloDto articuloDto, int id) { Articulo registro = findByArticleId(id); registro.setNombre(articuloDto.getNombre()); @@ -63,16 +61,13 @@ public Articulo update(ArticuloDto articuloDto, int id) { } @Override - @Transactional public void delete(int id) { - Articulo registro = articuloRepository.findById(id).orElse(null); - if (registro != null) - articuloRepository.delete(registro); + Articulo registro = findByArticleId(id); + articuloRepository.delete(registro); } private Articulo findByArticleId(int id) { - return articuloRepository.findById(id).orElseThrow( () -> new NoDataFoundException("No existe un articulo con ese id: %d".formatted(id))); } diff --git a/src/main/java/pe/edu/utp/service/UsuarioService.java b/src/main/java/pe/edu/utp/service/UsuarioService.java index c4ffca9..1ead8c5 100644 --- a/src/main/java/pe/edu/utp/service/UsuarioService.java +++ b/src/main/java/pe/edu/utp/service/UsuarioService.java @@ -3,16 +3,16 @@ 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 List findByEmail(String email, Pageable page); public Usuario findById(int id); - public Usuario update(Usuario usuario); - public Usuario save(Usuario usuario); + 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 bc7cb7f..5b7fb2e 100644 --- a/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java +++ b/src/main/java/pe/edu/utp/service/UsuarioServiceImpl.java @@ -1,7 +1,7 @@ package pe.edu.utp.service; -import io.jsonwebtoken.JwtException; -import lombok.RequiredArgsConstructor; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Pageable; @@ -9,27 +9,23 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import pe.edu.utp.exception.GeneralServiceException; -import pe.edu.utp.exception.NoDataFoundException; -import pe.edu.utp.exception.ValidateServiceException; + +import lombok.RequiredArgsConstructor; import pe.edu.utp.converter.UsuarioConverter; import pe.edu.utp.dto.LoginRequestDto; import pe.edu.utp.dto.LoginResponseDto; +import pe.edu.utp.dto.UsuarioRequestDto; +import pe.edu.utp.entity.Rol; import pe.edu.utp.entity.Usuario; +import pe.edu.utp.exception.EmailAlreadyException; +import pe.edu.utp.exception.NoDataFoundException; import pe.edu.utp.repository.UsuarioRepository; import pe.edu.utp.security.JwtService; -import pe.edu.utp.validator.UsuarioValidator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; @Service @RequiredArgsConstructor -public class UsuarioServiceImpl implements UsuarioService{ - - private static final Logger logger= LoggerFactory.getLogger(UsuarioServiceImpl.class); +public class UsuarioServiceImpl implements UsuarioService { private final UsuarioRepository usuarioRepository; @@ -41,126 +37,63 @@ public class UsuarioServiceImpl implements UsuarioService{ private final UsuarioConverter usuarioConverter; - - - @Override - public List findAll(Pageable page) throws Exception { - try { - return usuarioRepository.findAll(page).toList(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - throw (e instanceof ValidateServiceException || e instanceof NoDataFoundException) ? e : new GeneralServiceException(e.getMessage()); - } - } - - - @Override - public List findByEmail(String email, Pageable page) { - try { - return usuarioRepository.findByEmailContaining(email, page); - }catch (ValidateServiceException | NoDataFoundException e){ - logger.info(e.getMessage()); - throw e; - }catch(Exception e){ - logger.error(e.getMessage()); - throw new GeneralServiceException(e.getMessage()); - } + public List findAll(Pageable page) throws Exception { + return usuarioRepository.findAll(page).toList(); } @Override public Usuario findById(int id) { - try { - return usuarioRepository.findById(id).orElseThrow( - () -> new NoDataFoundException("No existe el usuario con id" + id) - ); - }catch (ValidateServiceException | NoDataFoundException e){ - logger.info(e.getMessage()); - throw e; - }catch (Exception e){ - logger.error(e.getMessage()); - throw new GeneralServiceException(e.getMessage()); - } + return usuarioRepository.findById(id).orElseThrow( + () -> new NoDataFoundException("No existe el usuario con id: %d".formatted(id))); } @Override - @Transactional(readOnly = true) - public Usuario update(Usuario usuario) { - - try{ - UsuarioValidator.save(usuario); - Usuario registroDB= usuarioRepository.findByEmail(usuario.getEmail()).orElseThrow( - ()->new NoDataFoundException("No existe el usuario para el email ingresado") - ); - if(registroDB!=null && !Objects.equals(registroDB.getId(), usuario.getId())){ - throw new ValidateServiceException("Ya existe un registro con el email " +usuario.getEmail()); - } - Usuario registro= usuarioRepository.findById(usuario.getId()).orElseThrow( - ()-> new NoDataFoundException("No existe el registro con el id: "+usuario.getId()) - ); - registro.setRol(usuario.getRol()); - usuarioRepository.save(registro); - return registro; - - }catch(ValidateServiceException | NoDataFoundException e){ - logger.info(e.getMessage()); - throw e; - }catch(Exception e){ - logger.error(e.getMessage()); - throw new GeneralServiceException(e.getMessage()); - } + 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))); + registro.setRol(Rol.valueOf(usuario.getRol())); + registro.setEmail(usuario.getEmail()); + registro.setPassword(encoder.encode(usuario.getPassword())); + usuarioRepository.save(registro); + return registro; + } @Override - @Transactional - public Usuario save(Usuario usuario) { - try{ - UsuarioValidator.save(usuario); - Optional reg= usuarioRepository.findByEmail(usuario.getEmail()); - if(reg.isPresent()){ - throw new ValidateServiceException("Ya existe un registro con el email :" +usuario.getEmail()); - } - String hashPassword=encoder.encode(usuario.getPassword()); - usuario.setPassword(hashPassword); - usuario.setActivo(true); - Usuario registro=usuarioRepository.save(usuario); - return registro; - }catch(ValidateServiceException | NoDataFoundException e) { - logger.info(e.getMessage()); - throw e; - }catch (Exception e) { - logger.error(e.getMessage()); - throw new GeneralServiceException(e.getMessage()); - - } + 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 - @Transactional(readOnly = true) public void delete(int id) { - try { - Usuario registro=usuarioRepository.findById(id).orElseThrow(()->new NoDataFoundException("No existe un registro con ese Id")); - usuarioRepository.delete(registro); - } catch (Exception e) { - logger.error(e.getMessage()); - throw new GeneralServiceException(e.getMessage()); - } - } + var userDb = findById(id); + usuarioRepository.delete(userDb); + } + @Override public LoginResponseDto login(LoginRequestDto loginRequestDto) { - try { - 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); - } catch (JwtException e) { - logger.info(e.getMessage(),e); - throw new ValidateServiceException(e.getMessage()); - }catch (Exception e) { - logger.info(e.getMessage(),e); - throw new ValidateServiceException(e.getMessage()); - } + 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 new file mode 100644 index 0000000..b647005 --- /dev/null +++ b/src/main/java/pe/edu/utp/util/ApiError.java @@ -0,0 +1,16 @@ +package pe.edu.utp.util; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ApiError { + 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 new file mode 100644 index 0000000..d778454 --- /dev/null +++ b/src/main/java/pe/edu/utp/util/ApiResponse.java @@ -0,0 +1,72 @@ +package pe.edu.utp.util; + +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; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ApiResponse { + private int status; + private String code; + private String message; + private LocalDateTime timestamp; + 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(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 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 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) --- + + // 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); + } + + // 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/main/java/pe/edu/utp/util/ConstantesHelpers.java b/src/main/java/pe/edu/utp/util/ConstantesHelpers.java deleted file mode 100644 index 1ac7849..0000000 --- a/src/main/java/pe/edu/utp/util/ConstantesHelpers.java +++ /dev/null @@ -1,8 +0,0 @@ -package pe.edu.utp.util; - -import lombok.Getter; - -@Getter -public class ConstantesHelpers { - public static final String MESSAGE_SUCCESS = "success"; -} diff --git a/src/main/java/pe/edu/utp/util/WrapperResponse.java b/src/main/java/pe/edu/utp/util/WrapperResponse.java deleted file mode 100644 index 3daa4d9..0000000 --- a/src/main/java/pe/edu/utp/util/WrapperResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package pe.edu.utp.util; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -public class WrapperResponse { - private boolean ok; - private String message; - private T body; - - public ResponseEntity> createResponse() { - return new ResponseEntity<>(this, HttpStatus.OK); - } - - public ResponseEntity> createResponse(HttpStatus status) { - return new ResponseEntity<>(this, status); - } -} diff --git a/src/main/java/pe/edu/utp/validator/UsuarioValidator.java b/src/main/java/pe/edu/utp/validator/UsuarioValidator.java deleted file mode 100644 index d066238..0000000 --- a/src/main/java/pe/edu/utp/validator/UsuarioValidator.java +++ /dev/null @@ -1,24 +0,0 @@ -package pe.edu.utp.validator; - -import pe.edu.utp.exception.ValidateServiceException; -import pe.edu.utp.entity.Usuario; - -public class UsuarioValidator { - public static void save(Usuario usuario){ - if(usuario.getEmail()==null) { - throw new ValidateServiceException("El email es requerido"); - } - if(usuario.getEmail().length()<=0) { - throw new ValidateServiceException("El email es requerido"); - } - if(usuario.getPassword()==null) { - throw new ValidateServiceException("El password es requerido"); - } - if(usuario.getPassword().length()<=0) { - throw new ValidateServiceException("El password es requerido"); - } - if(usuario.getRol()==null) { - throw new ValidateServiceException("El rol es requerido"); - } - } -} diff --git a/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java b/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java index c703603..f2bede3 100644 --- a/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java +++ b/src/test/java/pe/edu/utp/service/UsuarioServiceTest.java @@ -38,20 +38,4 @@ void Setup() { .build(); } - @Test - public void CreateUserTest() { - // Arrange - when(usuarioRepository.findByEmail(usuario.getEmail())).thenReturn(Optional.empty()); - when(usuarioRepository.save(any(Usuario.class))).thenReturn(usuario); - - // Act - Usuario savedUsuario = usuarioService.save(usuario); - - // Assert - assertNotNull(savedUsuario); - assertEquals(usuario.getEmail(), savedUsuario.getEmail()); - assertTrue(savedUsuario.isActivo()); - assertEquals(usuario.getRol(), savedUsuario.getRol()); - verify(usuarioRepository, times(1)).save(any(Usuario.class)); - } }