From a52b2f957d1a545410445ee0f08f4ad3843a204b Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Sun, 29 Dec 2024 17:32:16 -0500 Subject: [PATCH 01/25] Refactor and document services, validations, and repositories for crop management Implemented validations in crop services using Optional and custom validators. Documented all methods and DTOs with Javadoc and Swagger, enhancing API readability and documentation. Additionally, refactored MongoDB repositories with custom queries and added relevant documentation. --- .../api/Crops/Controller/CropController.java | 22 +- .../smartpot/com/api/Crops/Mapper/MCrop.java | 3 - .../api/Crops/Model/DAO/Repository/RCrop.java | 64 ++- .../api/Crops/Model/DAO/Service/SCrop.java | 447 +++++++++++------- .../api/Crops/Model/DAO/Service/SCropI.java | 35 +- .../com/api/Crops/Model/DTO/CropDTO.java | 26 + .../com/api/Crops/Model/Entity/Crop.java | 3 +- .../com/api/Crops/Model/Entity/Status.java | 2 +- .../com/api/Crops/Model/Entity/Type.java | 2 +- .../com/api/Crops/Validation/VCrop.java | 77 +++ .../com/api/Crops/Validation/VCropI.java | 19 + .../api/Users/Controller/UserController.java | 14 +- .../smartpot/com/api/Users/Mapper/MUser.java | 3 - .../api/Users/Model/DAO/Repository/RUser.java | 10 +- .../api/Users/Model/DAO/Service/SUser.java | 70 +-- .../api/Users/Model/DAO/Service/SUserI.java | 16 +- .../com/api/Users/Model/Entity/User.java | 18 - .../com/api/Users/Validation/VUser.java | 2 +- 18 files changed, 531 insertions(+), 302 deletions(-) create mode 100644 src/main/java/smartpot/com/api/Crops/Validation/VCrop.java create mode 100644 src/main/java/smartpot/com/api/Crops/Validation/VCropI.java diff --git a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java index 7c69839..3b9d825 100644 --- a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java +++ b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java @@ -1,6 +1,7 @@ package smartpot.com.api.Crops.Controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -67,7 +68,8 @@ public CropController(SCropI serviceCrop) { description = "No se pudo crear el cultivo debido a un error.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity createCrop(@RequestBody CropDTO newCropDto) { + public ResponseEntity createCrop(@Parameter(description = "Datos del nuevo cultivo que se va a crear. Debe incluir tipo y usuario asociado.", + required = true) @RequestBody CropDTO newCropDto) { try { return new ResponseEntity<>(serviceCrop.createCrop(newCropDto), HttpStatus.CREATED); } catch (Exception e) { @@ -107,7 +109,7 @@ public ResponseEntity createCrop(@RequestBody CropDTO newCropDto) { }) public ResponseEntity getAllCrops() { try { - return new ResponseEntity<>(serviceCrop.getCrops(), HttpStatus.OK); + return new ResponseEntity<>(serviceCrop.getAllCrops(), HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); } @@ -145,7 +147,7 @@ public ResponseEntity getAllCrops() { description = "Cultivo no encontrado con el ID especificado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getCropById(@PathVariable String id) { + public ResponseEntity getCropById(@Parameter(description = "ID único del cultivo", required = true) @PathVariable String id) { try { return new ResponseEntity<>(serviceCrop.getCropById(id), HttpStatus.OK); } catch (Exception e) { @@ -183,7 +185,7 @@ public ResponseEntity getCropById(@PathVariable String id) { description = "No se encontraron cultivos con el estado proporcionado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getCropsByStatus(@PathVariable String status) { + public ResponseEntity getCropsByStatus(@Parameter(description = "Estado de los cultivos a buscar", required = true) @PathVariable String status) { try { return new ResponseEntity<>(serviceCrop.getCropsByStatus(status), HttpStatus.OK); } catch (Exception e) { @@ -221,7 +223,7 @@ public ResponseEntity getCropsByStatus(@PathVariable String status) { description = "No se encontraron cultivos con el tipo proporcionado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getCropsByType(@PathVariable String type) { + public ResponseEntity getCropsByType(@Parameter(description = "Tipo de los cultivos a buscar", required = true) @PathVariable String type) { try { return new ResponseEntity<>(serviceCrop.getCropsByType(type), HttpStatus.OK); } catch (Exception e) { @@ -260,7 +262,7 @@ public ResponseEntity getCropsByType(@PathVariable String type) { description = "No se encontraron cultivos para el usuario con el ID especificado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getCropByUser(@PathVariable String id) { + public ResponseEntity getCropByUser(@PathVariable @Parameter(description = "ID único del usuario para buscar sus cultivos", required = true) String id) { try { return new ResponseEntity<>(serviceCrop.getCropsByUser(id), HttpStatus.OK); } catch (Exception e) { @@ -301,7 +303,7 @@ public ResponseEntity getCropByUser(@PathVariable String id) { description = "No se encontraron cultivos para el usuario con el ID especificado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity countCropsByUser(@PathVariable String id) { + public ResponseEntity countCropsByUser(@PathVariable @Parameter(description = "ID único del usuario para buscar sus cultivos", required = true) String id) { try { return new ResponseEntity<>(serviceCrop.countCropsByUser(id), HttpStatus.OK); } catch (Exception e) { @@ -342,7 +344,7 @@ public ResponseEntity countCropsByUser(@PathVariable String id) { description = "Cultivo no encontrado o error en la actualización.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity updateCrop(@PathVariable String id, @RequestBody CropDTO cropDetails) { + public ResponseEntity updateCrop(@Parameter(description = "ID único del cultivo a actualizar", required = true) @PathVariable String id, @Parameter(description = "Información del cultivo a actualizar", required = true) @RequestBody CropDTO cropDetails) { try { return new ResponseEntity<>(serviceCrop.updatedCrop(id, cropDetails), HttpStatus.OK); } catch (Exception e) { @@ -382,9 +384,9 @@ public ResponseEntity updateCrop(@PathVariable String id, @RequestBody CropDT description = "Cultivo no encontrado o error en la eliminación.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity deleteCrop(@PathVariable String id) { + public ResponseEntity deleteCrop(@Parameter(description = "ID único del cultivo a eliminar", required = true) @PathVariable String id) { try { - return new ResponseEntity<>(serviceCrop.deleteCrop(serviceCrop.getCropById(id)), HttpStatus.NO_CONTENT); + return new ResponseEntity<>(serviceCrop.deleteCrop(id), HttpStatus.NO_CONTENT); } catch (Exception e) { return new ResponseEntity<>(new ErrorResponse("Error al eliminar el cultivo con ID '" + id + "' [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); } diff --git a/src/main/java/smartpot/com/api/Crops/Mapper/MCrop.java b/src/main/java/smartpot/com/api/Crops/Mapper/MCrop.java index bc6e3d3..1c77bc6 100644 --- a/src/main/java/smartpot/com/api/Crops/Mapper/MCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Mapper/MCrop.java @@ -3,14 +3,11 @@ import org.bson.types.ObjectId; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; import smartpot.com.api.Crops.Model.DTO.CropDTO; import smartpot.com.api.Crops.Model.Entity.Crop; @Mapper(componentModel = "spring") public interface MCrop { - MCrop INSTANCE = Mappers.getMapper(MCrop.class); - @Mapping(source = "id", target = "id", qualifiedByName = "stringToObjectId") @Mapping(source = "user", target = "user", qualifiedByName = "stringToObjectId") Crop toEntity(CropDTO cropDTO); diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java index 7f67882..796b41d 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java @@ -4,31 +4,65 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; import smartpot.com.api.Crops.Model.Entity.Crop; import java.util.List; -import java.util.Optional; +/** + * Repositorio para la gestión de la entidad {@link Crop} en la base de datos MongoDB. + *

+ * Esta interfaz extiende {@link MongoRepository}, lo que proporciona métodos CRUD básicos + * para interactuar con la base de datos MongoDB de manera sencilla. + * Además, incluye consultas personalizadas para buscar cultivos por diferentes atributos + * como tipo, estado y usuario. + *

+ *

+ * Las consultas personalizadas están anotadas con {@link Query}, que utilizan la sintaxis de MongoDB + * para realizar búsquedas más específicas en los campos de la colección de cultivos. + *

+ * + * @see MongoRepository + * @see Crop + */ @Repository public interface RCrop extends MongoRepository { - - @Query("{ '_id' : ?0 }") - Optional findById(ObjectId id); - + /** + * Busca una lista de cultivos cuyo tipo coincida exactamente con el tipo proporcionado. + * + *

Esta consulta busca cultivos cuyo campo 'type' coincida exactamente con el valor del parámetro + * proporcionado, lo que permite filtrar los cultivos por su tipo.

+ * + * @param type El tipo de cultivo a buscar en el campo 'type'. + * @return Una lista de cultivos cuyo tipo coincida exactamente. + * @see Query + */ @Query("{ 'type' : ?0 }") List findByType(String type); + /** + * Busca una lista de cultivos cuyo estado coincida exactamente con el estado proporcionado. + * + *

Esta consulta busca cultivos cuyo campo 'status' coincida exactamente con el valor del parámetro + * proporcionado, lo que permite filtrar los cultivos por su estado (por ejemplo, 'activo', 'inactivo', etc.).

+ * + * @param status El estado del cultivo a buscar en el campo 'status'. + * @return Una lista de cultivos cuyo estado coincida exactamente. + * @see Query + */ @Query("{ 'status' : ?0 }") List findByStatus(String status); - /*@Transactional - @Query("{ '_id' : ?0 }") - void deleteById(ObjectId id); -*/ - @Transactional - @Query("{ '_id' : ?0 }") - Crop updateUser(ObjectId id, Crop crop); - - + /** + * Busca una lista de cultivos cuyo campo 'user' coincida con el ID de usuario proporcionado. + * + *

Esta consulta busca cultivos cuyo campo 'user' coincida con el ID del usuario proporcionado. + * Permite filtrar los cultivos asociados a un usuario específico.

+ * + * @param id El ID del usuario (en formato {@link ObjectId}) cuyo campo 'user' se desea buscar. + * @return Una lista de cultivos asociados al usuario con el ID proporcionado. + * @see Query + * @see ObjectId + */ + @Query("{ 'user': ?0}") + List findByUser(ObjectId id); } diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index 94643c8..2bd4561 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -1,27 +1,21 @@ package smartpot.com.api.Crops.Model.DAO.Service; +import jakarta.validation.ValidationException; import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; +import smartpot.com.api.Crops.Mapper.MCrop; import smartpot.com.api.Crops.Model.DAO.Repository.RCrop; import smartpot.com.api.Crops.Model.DTO.CropDTO; -import smartpot.com.api.Crops.Model.Entity.Crop; -import smartpot.com.api.Crops.Model.Entity.Status; -import smartpot.com.api.Crops.Model.Entity.Type; -import smartpot.com.api.Exception.ApiException; -import smartpot.com.api.Exception.ApiResponse; +import smartpot.com.api.Crops.Validation.VCropI; import smartpot.com.api.Users.Model.DAO.Service.SUserI; -import smartpot.com.api.Users.Model.DTO.UserDTO; -import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; +import java.util.Optional; +import java.util.stream.Collectors; /** @@ -37,227 +31,336 @@ public class SCrop implements SCropI { private final RCrop repositoryCrop; private final SUserI serviceUser; + private final MCrop mapperCrop; + private final VCropI validatorCrop; + /** + * Constructor del servicio de cultivos. + * + *

Inyecta las dependencias necesarias para realizar las operaciones relacionadas con los cultivos, + * incluyendo el repositorio de cultivos {@link RCrop}, el servicio de usuarios {@link SUserI}, + * el convertidor de cultivos {@link MCrop}, y el validador de cultivos {@link VCropI}.

+ * + * @param repositoryCrop El repositorio que maneja las operaciones de base de datos para cultivos. + * @param serviceUser El servicio de usuarios, utilizado para interactuar con los detalles de los usuarios. + * @param mapperCrop El convertidor que convierte las entidades de cultivos a objetos DTO correspondientes. + * @param validatorCrop El validador que valida los datos relacionados con los cultivos. + * @see RCrop + * @see SUserI + * @see MCrop + * @see VCropI + */ @Autowired - public SCrop(RCrop repositoryCrop, SUserI serviceUser) { + public SCrop(RCrop repositoryCrop, SUserI serviceUser, MCrop mapperCrop, VCropI validatorCrop) { this.repositoryCrop = repositoryCrop; this.serviceUser = serviceUser; + this.mapperCrop = mapperCrop; + this.validatorCrop = validatorCrop; } /** - * Este metodo maneja el ID como un ObjectId de MongoDB. Si el ID es válido como ObjectId, - * se utiliza en la búsqueda como tal. + * Crea un nuevo cultivo en la base de datos a partir de un objeto {@link CropDTO}. * - * @param id El identificador del cultivo a buscar. Se recibe como String para evitar errores de conversion. - * @return El cultivo correspondiente al id proporcionado. - * @throws ResponseStatusException Si el id proporcionado no es válido o no se encuentra el cultivo. - * @throws Exception Si no se encuentra el cultivo con el id proporcionado. + *

Este método recibe un objeto {@link CropDTO}, valida el tipo del cultivo y el ID del usuario + * asociado utilizando el validador {@link VCropI}. Si las validaciones son correctas, se asigna el estado + * por defecto "Unknown" al cultivo y se persiste en la base de datos utilizando el repositorio {@link RCrop}. + * Finalmente, el método mapea la entidad guardada nuevamente a un {@link CropDTO} para devolverlo como respuesta.

+ * + *

Si las validaciones fallan, se lanza una {@link ValidationException} con los detalles del error. + * Si el cultivo ya existe, se lanza una {@link Exception} indicando que el cultivo no puede ser creado.

+ * + * @param cropDTO El objeto {@link CropDTO} que contiene los datos del cultivo a crear. Este parámetro es obligatorio. + * @return El objeto {@link CropDTO} que representa el cultivo creado, con el estado actualizado a "Unknown". + * @throws Exception Si el cultivo ya existe o si ocurre algún otro error durante la creación. + * @throws ValidationException Si las validaciones del tipo o ID del usuario fallan según el validador {@link VCropI}. + * @see CropDTO + * @see VCropI + * @see RCrop + * @see MCrop */ @Override - public Crop getCropById(String id) { - if (!ObjectId.isValid(id)) { - throw new ApiException(new ApiResponse( - "El cultivo con id '" + id + "' no es válido. Asegúrate de que tiene 24 caracteres y solo incluye dígitos hexadecimales (0-9, a-f, A-F).", - HttpStatus.BAD_REQUEST.value() - )); - } - return repositoryCrop.findById(new ObjectId(id)) - .orElseThrow(() -> new ApiException( - new ApiResponse("El cultivo con id '" + id + "' no fue encontrado.", - HttpStatus.NOT_FOUND.value()) - )); + public CropDTO createCrop(CropDTO cropDTO) throws Exception { + return Optional.of(cropDTO) + .map(ValidCropDTO -> { + validatorCrop.validateType(ValidCropDTO.getType()); + validatorCrop.validateUser(ValidCropDTO.getUser()); + if (validatorCrop.isValid()) { + throw new ValidationException(validatorCrop.getErrors().toString()); + } + validatorCrop.Reset(); + return ValidCropDTO; + }) + .map(dto -> { + dto.setStatus("Unknown"); + return dto; + }) + .map(mapperCrop::toEntity) + .map(repositoryCrop::save) + .map(mapperCrop::toDTO) + .orElseThrow(() -> new Exception("El cultivo ya existe")); } /** - * Obtiene todos los cultivos almacenados en el sistema. + * Obtiene todos los cultivos registrados en la base de datos y los convierte en objetos DTO. + * + *

Este método consulta todos los cultivos almacenados en la base de datos mediante el repositorio {@link RCrop}. + * Si la lista de cultivos está vacía, se lanza una excepción. Los cultivos obtenidos se mapean a objetos + * {@link CropDTO} utilizando el convertidor {@link MCrop}.

* - * @return Lista de todos los cultivos existentes + * @return Una lista de objetos {@link CropDTO} que representan todos los cultivos en la base de datos. + * @throws Exception Si no existen cultivos registrados en la base de datos. + * + * @see CropDTO + * @see RCrop + * @see MCrop */ @Override - public List getCrops() { - List crops = repositoryCrop.findAll(); - if (crops == null || crops.isEmpty()) { - throw new ApiException(new ApiResponse( - "No se encontro ningun cultivo en la db", - HttpStatus.NOT_FOUND.value() - )); - } - return crops; + public List getAllCrops() throws Exception { + return Optional.of(repositoryCrop.findAll()) + .filter(crops -> !crops.isEmpty()) + .map(crops -> crops.stream() + .map(mapperCrop::toDTO) + .collect(Collectors.toList())) + .orElseThrow(() -> new Exception("No existe ningún cultivo")); } /** - * Busca todos los cultivos asociados a un usuario específico. + * Obtiene un cultivo de la base de datos a partir de su identificador. + * + *

Este método busca un cultivo en la base de datos utilizando el ID proporcionado. + * Primero, valida que el ID sea válido utilizando el validador {@link VCropI}. + * Si el ID es válido, realiza una búsqueda en la base de datos usando el repositorio {@link RCrop}. + * Si el cultivo existe, se convierte a un objeto {@link CropDTO} utilizando el convertidor {@link MCrop}. + * Si el cultivo no existe o el ID no es válido, se lanza una excepción correspondiente.

* - * @param id del Usuario propietario de los cultivos - * @return Lista de cultivos pertenecientes al usuario + * @param id El identificador del cultivo que se desea obtener. El ID debe ser una cadena que representa un {@link ObjectId}. + * @return Un objeto {@link CropDTO} que representa el cultivo encontrado. + * @throws Exception Si el cultivo no existe en la base de datos o si el ID no es válido. + * @throws ValidationException Si el ID proporcionado no es válido según las reglas de validación del validador {@link VCropI}. + * + * @see CropDTO + * @see VCropI + * @see RCrop + * @see MCrop */ @Override - public List getCropsByUser(String id) throws Exception { - UserDTO user = serviceUser.getUserById(id); - List crops = repositoryCrop.findAll(); - List cropsUser = new ArrayList<>(); - ObjectId userId = new ObjectId(user.getId()); - for (Crop crop : crops) { - if (crop.getUser().equals(userId)) { - cropsUser.add(crop); - } - } - if (cropsUser.isEmpty()) { - throw new ApiException(new ApiResponse( - "No se encuentra ningun Cultivo perteneciente al usuario con el id: '" + id + "'.", - HttpStatus.NOT_FOUND.value() - )); - } - return cropsUser; + public CropDTO getCropById(String id) throws Exception { + return Optional.of(id) + .map(ValidCropId -> { + validatorCrop.validateId(ValidCropId); + if (validatorCrop.isValid()) { + throw new ValidationException(validatorCrop.getErrors().toString()); + } + validatorCrop.Reset(); + return new ObjectId(ValidCropId); + }) + .map(repositoryCrop::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .map(mapperCrop::toDTO) + .orElseThrow(() -> new Exception("El cultivo no existe")); } /** - * Busca cultivos por su tipo . + * Obtiene los cultivos asociados a un usuario específico mediante su ID. + * + *

Este método busca todos los cultivos relacionados con un usuario en la base de datos utilizando el + * ID del usuario. Primero, valida que el ID del usuario sea válido llamando al servicio de usuarios {@link SUserI}. + * Si el usuario existe, se realiza una búsqueda de los cultivos asociados a ese usuario a través del repositorio {@link RCrop}. + * Si se encuentran cultivos, se mapean a objetos {@link CropDTO} mediante el convertidor {@link MCrop}. + * Si el usuario no tiene cultivos o el ID del usuario es inválido, se lanza una excepción correspondiente.

* - * @param type Tipo del cultivo - * @return Lista de cultivos que coinciden con el tipo especificado + * @param id El identificador del usuario cuyos cultivos se desean obtener. El ID debe ser una cadena que representa un {@link ObjectId}. + * @return Una lista de objetos {@link CropDTO} que representan los cultivos asociados al usuario. + * @throws Exception Si el usuario no tiene cultivos o si el ID del usuario es inválido o no existe. + * + * @see CropDTO + * @see SUserI + * @see RCrop + * @see MCrop */ @Override - public List getCropsByType(String type) { - boolean isValidSType = Stream.of(Type.values()) - .anyMatch(r -> r.name().equalsIgnoreCase(type)); - if (!isValidSType) { - throw new ApiException(new ApiResponse( - "El Type '" + type + "' no es válido.", - HttpStatus.BAD_REQUEST.value())); - } - List cropsByType = repositoryCrop.findByType(type); - return repositoryCrop.findByType(type); + public List getCropsByUser(String id) throws Exception { + return Optional.of(serviceUser.getUserById(id)) + .map(validUser -> new ObjectId(validUser.getId())) + .map(repositoryCrop::findByUser) + .filter(crops -> !crops.isEmpty()) + .map(crops -> crops.stream() + .map(mapperCrop::toDTO) + .collect(Collectors.toList())) + .orElseThrow(() -> new Exception("No tiene ningún cultivo")); } /** - * Cuenta el número total de cultivos que tiene un usuario. + * Obtiene una lista de cultivos de la base de datos según el tipo proporcionado. + * + *

Este método busca cultivos en la base de datos utilizando el tipo de cultivo proporcionado como parámetro. + * Primero, valida que el tipo de cultivo sea válido mediante el validador {@link VCropI}. + * Si el tipo es válido, realiza una búsqueda en la base de datos usando el repositorio {@link RCrop}. + * Si se encuentran cultivos con el tipo proporcionado, se mapean a objetos {@link CropDTO} usando el convertidor {@link MCrop}. + * Si no se encuentran cultivos o si el tipo no es válido, se lanza una excepción correspondiente.

+ * + * @param type El tipo de cultivo que se desea obtener. + * @return Una lista de objetos {@link CropDTO} que representan los cultivos encontrados con el tipo proporcionado. + * @throws Exception Si no se encuentran cultivos con el tipo proporcionado o si ocurre algún otro error. + * @throws ValidationException Si el tipo de cultivo proporcionado no es válido según las reglas de validación del validador {@link VCropI}. * - * @param id del Usuario del que se quieren contar los cultivos - * @return Número total de cultivos del usuario + * @see CropDTO + * @see VCropI + * @see RCrop + * @see MCrop */ @Override - public long countCropsByUser(String id) throws Exception { - System.out.println("//////////////////////////////////////////////" + getCropsByUser(id).size()); - return getCropsByUser(id).size(); - + public List getCropsByType(String type) throws Exception { + return Optional.of(type) + .map(ValidType -> { + validatorCrop.validateType(ValidType); + if (validatorCrop.isValid()) { + throw new ValidationException(validatorCrop.getErrors().toString()); + } + validatorCrop.Reset(); + return ValidType; + }) + .map(repositoryCrop::findByType) + .filter(crops -> !crops.isEmpty()) + .map(crops -> crops.stream() + .map(mapperCrop::toDTO) + .collect(Collectors.toList())) + .orElseThrow(() -> new Exception("No existen cultivos")); } /** - * Busca cultivos por su estado actual. + * Cuenta la cantidad de cultivos asociados a un usuario específico. + * + *

Este método obtiene la lista de cultivos asociados a un usuario mediante su ID utilizando el método + * {@link #getCropsByUser(String)}, y luego cuenta la cantidad de cultivos encontrados. Si el usuario no tiene cultivos, + * el método devolverá un valor de 0.

+ * + * @param id El identificador del usuario cuyos cultivos se desean contar. El ID debe ser una cadena que representa un {@link ObjectId}. + * @return El número de cultivos asociados al usuario especificado. + * @throws Exception Sí ocurre algún error al obtener los cultivos del usuario. * - * @param status Estado del cultivo a buscar - * @return Lista de cultivos que se encuentran en el estado especificado + * @see #getCropsByUser(String) */ @Override - public List getCropsByStatus(String status) { - boolean isValidStatus = Stream.of(Status.values()) - .anyMatch(r -> r.name().equalsIgnoreCase(status)); - if (!isValidStatus) { - throw new ApiException(new ApiResponse( - "El Status '" + status + "' no es válido.", - HttpStatus.BAD_REQUEST.value())); - } - List cropsByStatus = repositoryCrop.findByStatus(status); - return cropsByStatus; + public long countCropsByUser(String id) throws Exception { + return Optional.of(getCropsByUser(id)) + .stream() + .count(); } /** - * Crea un cultivo en el sistema. + * Obtiene una lista de cultivos de la base de datos según el estado proporcionado. + * + *

Este método busca cultivos en la base de datos utilizando el estado de cultivo proporcionado como parámetro. + * Primero, valida que el estado sea válido mediante el validador {@link VCropI}. Si el estado es válido, + * realiza una búsqueda en la base de datos usando el repositorio {@link RCrop}. Si se encuentran cultivos con + * el estado proporcionado, se mapean a objetos {@link CropDTO} utilizando el convertidor {@link MCrop}. + * Si no se encuentran cultivos o si el estado no es válido, se lanza una excepción correspondiente.

+ * + * @param status El estado del cultivo que se desea obtener. + * @return Una lista de objetos {@link CropDTO} que representan los cultivos encontrados con el estado proporcionado. + * @throws Exception Si no se encuentran cultivos con el estado proporcionado o si ocurre algún otro error. + * @throws ValidationException Si el estado proporcionado no es válido según las reglas de validación del validador {@link VCropI}. * - * @return Cultivo guardado + * @see CropDTO + * @see VCropI + * @see RCrop + * @see MCrop */ @Override - public Crop createCrop(CropDTO newCropDto) throws Exception { - serviceUser.getUserById(newCropDto.getUser()); - Crop newCrop = cropDtotoCrop(newCropDto); - boolean isValidStatus = Stream.of(Status.values()) - .anyMatch(r -> r.name().equalsIgnoreCase(newCrop.getStatus().name())); - if (!isValidStatus) { - throw new ApiException(new ApiResponse( - "El Status '" + newCrop.getStatus().name() + "' no es válido.", - HttpStatus.BAD_REQUEST.value())); - } - boolean isValidSType = Stream.of(Type.values()) - .anyMatch(r -> r.name().equalsIgnoreCase(newCrop.getType().name())); - if (!isValidSType) { - throw new ApiException(new ApiResponse( - "El Type '" + newCrop.getType().name() + "' no es válido.", - HttpStatus.BAD_REQUEST.value())); - } - return repositoryCrop.save(newCrop); + public List getCropsByStatus(String status) throws Exception { + return Optional.of(status) + .map(ValidStatus -> { + validatorCrop.validateStatus(ValidStatus); + if (validatorCrop.isValid()) { + throw new ValidationException(validatorCrop.getErrors().toString()); + } + validatorCrop.Reset(); + return ValidStatus; + }) + .map(repositoryCrop::findByStatus) + .filter(crops -> !crops.isEmpty()) + .map(crops -> crops.stream() + .map(mapperCrop::toDTO) + .collect(Collectors.toList())) + .orElseThrow(() -> new Exception("No existen cultivos")); } /** - * Actualiza la información de un Crop existente. + * Actualiza un cultivo existente en la base de datos con los nuevos datos proporcionados en un objeto {@link CropDTO}. * - * @param id El identificador del Crop a actualizar. - * @return El Crop actualizado después de guardarlo en el servicio. + *

Este método recibe el ID del cultivo a actualizar y un objeto {@link CropDTO} con los nuevos datos. + * Primero, busca el cultivo existente usando el ID proporcionado. Luego, actualiza los campos que no sean + * nulos en el objeto {@link CropDTO}, manteniendo los valores existentes cuando el campo es nulo. Después, + * valida los nuevos datos utilizando el validador {@link VCropI}. Si las validaciones son correctas, el cultivo + * se actualiza en la base de datos. Finalmente, el método devuelve el objeto actualizado {@link CropDTO}.

+ * + *

Si las validaciones no pasan o si ocurre un error durante el proceso de actualización, se lanza una + * {@link ValidationException} o una {@link Exception}, respectivamente.

+ * + * @param id El ID único del cultivo que se desea actualizar. Este parámetro es obligatorio. + * @param updateCrop El objeto {@link CropDTO} que contiene los nuevos datos para actualizar el cultivo. + * Los campos nulos no modificarán el valor existente. + * @return El objeto {@link CropDTO} actualizado que representa el cultivo después de la actualización. + * @throws Exception Si ocurre un error al intentar actualizar el cultivo, como si el cultivo no existe. + * @throws ValidationException Si alguna de las validaciones del estado, tipo o usuario falla según el validador {@link VCropI}. + * + * @see CropDTO + * @see VCropI + * @see RCrop + * @see MCrop */ @Override - public Crop updatedCrop(String id, CropDTO cropDto) throws Exception { - serviceUser.getUserById(cropDto.getUser()); - Crop updatedCrop = cropDtotoCrop(cropDto); - boolean isValidStatus = Stream.of(Status.values()) - .anyMatch(r -> r.name().equalsIgnoreCase(updatedCrop.getStatus().name())); - if (!isValidStatus) { - throw new ApiException(new ApiResponse( - "El Status '" + updatedCrop.getStatus().name() + "' no es válido.", - HttpStatus.BAD_REQUEST.value())); - } - boolean isValidSType = Stream.of(Type.values()) - .anyMatch(r -> r.name().equalsIgnoreCase(updatedCrop.getType().name())); - if (!isValidSType) { - throw new ApiException(new ApiResponse( - "El Type '" + updatedCrop.getType().name() + "' no es válido.", - HttpStatus.BAD_REQUEST.value())); - } - return repositoryCrop.updateUser(getCropById(id).getId(), updatedCrop); - } + public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { + CropDTO existingCrop = getCropById(id); + return Optional.of(updateCrop) + .map(dto -> { + existingCrop.setType(dto.getType() != null ? dto.getType() : existingCrop.getType()); + existingCrop.setStatus(dto.getStatus() != null ? dto.getStatus() : existingCrop.getStatus()); + existingCrop.setUser(dto.getUser() != null ? dto.getUser() : existingCrop.getUser()); + return existingCrop; + }) + .map(dto -> { + validatorCrop.validateStatus(dto.getStatus()); + validatorCrop.validateType(dto.getType()); + validatorCrop.validateUser(dto.getUser()); + if (!validatorCrop.isValid()) { + throw new ValidationException(validatorCrop.getErrors().toString()); + } - private Crop cropDtotoCrop(CropDTO cropDto) { - Crop crop = new Crop(); - crop.setType(Type.valueOf(cropDto.getType())); - crop.setStatus(Status.valueOf(cropDto.getStatus())); - crop.setUser(new ObjectId(cropDto.getUser())); - return crop; + validatorCrop.Reset(); + return dto; + }) + .map(mapperCrop::toEntity) + .map(repositoryCrop::save) + .map(mapperCrop::toDTO) + .orElseThrow(() -> new Exception("El cultivo no se pudo actualizar")); } /** - * Elimina un cultivo existente por su identificador. + * Elimina un cultivo de la base de datos según el ID proporcionado. * - * @param id Es el identificador del cultivo que se desea eliminar. + *

Este método recibe el ID de un cultivo y lo busca en la base de datos. Si el cultivo existe, + * se elimina de la base de datos utilizando el repositorio {@link RCrop}. Después de eliminarlo, + * se devuelve un mensaje confirmando la eliminación exitosa del cultivo. Si el cultivo no existe, + * se lanza una {@link Exception} indicando que el cultivo no fue encontrado.

+ * + * @param id El ID único del cultivo que se desea eliminar. + * @return Un mensaje de confirmación que indica que el cultivo con el ID proporcionado fue eliminado correctamente. + * @throws Exception Si el cultivo no existe en la base de datos o si ocurre algún otro error durante la eliminación. + * + * @see RCrop */ - /* public void deleteCrop(String id) { - if (!ObjectId.isValid(id)) { - throw new ApiException(new ApiResponse( - "El ID '" + id + "' no es válido. Asegúrate de que tiene 24 caracteres y solo incluye dígitos hexadecimales (0-9, a-f, A-F).", - HttpStatus.BAD_REQUEST.value() - )); - } - Crop existingCrop = repositoryCrop.findById(new ObjectId(id)) - .orElseThrow(() -> new ApiException( - new ApiResponse("El usuario con ID '" + id + "' no fue encontrado.", - HttpStatus.NOT_FOUND.value()) - )); - - repositoryCrop.deleteById(existingCrop.getId()); - }*/ @Override - public ResponseEntity deleteCrop(Crop existingCrop) { - try { - repositoryCrop.deleteById(existingCrop.getId()); - return ResponseEntity.status(HttpStatus.OK.value()).body( - new ApiResponse("El cultivo con ID '" + existingCrop.getId() + "' fue eliminado.", - HttpStatus.OK.value()) - ); - } catch (Exception e) { - log.error("e: ", e); - throw new ApiException( - new ApiResponse("No se pudo eliminar el usuario con ID '" + existingCrop.getId() + "'.", - HttpStatus.INTERNAL_SERVER_ERROR.value())); - } + public String deleteCrop(String id) throws Exception { + return Optional.of(getCropById(id)) + .map(user -> { + repositoryCrop.deleteById(new ObjectId(user.getId())); + return "El Cultivo con ID '" + id + "' fue eliminado."; + }) + .orElseThrow(() -> new Exception("El Cultivo no existe.")); } } diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java index 80440f7..53e04f0 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java @@ -1,43 +1,26 @@ package smartpot.com.api.Crops.Model.DAO.Service; -import org.springframework.http.ResponseEntity; import smartpot.com.api.Crops.Model.DTO.CropDTO; -import smartpot.com.api.Crops.Model.Entity.Crop; -import smartpot.com.api.Exception.ApiResponse; import java.util.List; public interface SCropI { - Crop getCropById(String id); - List getCrops(); + List getAllCrops() throws Exception; - List getCropsByUser(String id) throws Exception; + CropDTO getCropById(String id) throws Exception; - List getCropsByType(String type); + List getCropsByUser(String id) throws Exception; - long countCropsByUser(String id) throws Exception; + List getCropsByType(String type) throws Exception; - List getCropsByStatus(String status); + long countCropsByUser(String id) throws Exception; - Crop createCrop(CropDTO newCropDto) throws Exception; + List getCropsByStatus(String status) throws Exception; - Crop updatedCrop(String id, CropDTO cropDto) throws Exception; + CropDTO createCrop(CropDTO newCropDto) throws Exception; - /* public void deleteCrop(String id) { - if (!ObjectId.isValid(id)) { - throw new ApiException(new ApiResponse( - "El ID '" + id + "' no es válido. Asegúrate de que tiene 24 caracteres y solo incluye dígitos hexadecimales (0-9, a-f, A-F).", - HttpStatus.BAD_REQUEST.value() - )); - } - Crop existingCrop = repositoryCrop.findById(new ObjectId(id)) - .orElseThrow(() -> new ApiException( - new ApiResponse("El usuario con ID '" + id + "' no fue encontrado.", - HttpStatus.NOT_FOUND.value()) - )); + CropDTO updatedCrop(String id, CropDTO cropDto) throws Exception; - repositoryCrop.deleteById(existingCrop.getId()); - }*/ - ResponseEntity deleteCrop(Crop existingCrop); + String deleteCrop(String id) throws Exception; } diff --git a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java index 15205d3..6c68373 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java @@ -1,11 +1,37 @@ package smartpot.com.api.Crops.Model.DTO; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * DTO para representar los datos de un cultivo. + * Este DTO es utilizado para transferir la información de un cultivo + * entre diferentes capas del sistema, especialmente para su visualización + * o manipulación en las operaciones CRUD de cultivos. + */ @Data +@RequiredArgsConstructor +@Getter +@Setter public class CropDTO { + + @Schema(description = "ID único del cultivo, generado automáticamente por la base de datos.", + example = "60b63b8f3e111f8d44d45e72", hidden = true) private String id; + + @Schema(description = "Estado actual del cultivo.", + example = "Perfect_plant", hidden = true) private String status; + + @Schema(description = "Tipo de cultivo.", + example = "TOMATO") private String type; + + @Schema(description = "ID del usuario asociado a este cultivo. Este campo se utiliza para asociar un cultivo a un usuario específico del sistema.", + example = "676ae2a9b909de5f9607fcb6") private String user; } diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java index 40a695d..e6a7346 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java @@ -8,6 +8,7 @@ import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; @@ -40,7 +41,7 @@ public class Crop implements Serializable { @Field("type") private Type type; - /*@DBRef*/ + @DBRef @NotNull(message = "El cultivo debe pertenecer a un usuario") @Field("user") private ObjectId user; diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java index f447c33..5b155f7 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java @@ -2,5 +2,5 @@ public enum Status { Dead, Extreme_decomposition, Severe_deterioration, Moderate_deterioration, Healthy_state, intermittent, - Moderate_health, Good_health, Very_healthy, Excellent, Perfect_plant + Moderate_health, Good_health, Very_healthy, Excellent, Perfect_plant, Unknown } diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java index 1e4d894..e43c0e9 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java @@ -1,5 +1,5 @@ package smartpot.com.api.Crops.Model.Entity; public enum Type { - TOMATTO, LETTUCE + TOMATO, LETTUCE } diff --git a/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java b/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java new file mode 100644 index 0000000..f2f06b7 --- /dev/null +++ b/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java @@ -0,0 +1,77 @@ +package smartpot.com.api.Crops.Validation; + +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class VCrop implements VCropI { + /** + * Indica si la validación fue exitosa. + */ + public boolean valid; + + /** + * Lista de errores de validación. + */ + public List errors; + + /** + * Constructor de la clase de validación de usuario. + * Inicializa el estado de validación a "válido" y crea una lista vacía de errores. + */ + public VCrop() { + this.valid = true; + this.errors = new ArrayList<>(); + } + + /** + * Devuelve el estado de validez. + * + * @return true si el cultivo es válido, false si hay errores de validación. + */ + @Override + public boolean isValid() { + return valid; + } + + @Override + public void validateId(String id) { + + } + + /** + * Obtiene la lista de errores encontrados durante la validación. + * + * @return Una lista de cadenas con los mensajes de error. + */ + @Override + public List getErrors() { + return errors; + } + + /** + * Resetea el estado de la validación, marcando el cultivo como válido y limpiando la lista de errores. + */ + @Override + public void Reset() { + valid = true; + errors = new ArrayList<>(); + } + + @Override + public void validateType(String type) { + + } + + @Override + public void validateStatus(String validStatus) { + + } + + @Override + public void validateUser(String user) { + + } +} diff --git a/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java b/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java new file mode 100644 index 0000000..ee712ed --- /dev/null +++ b/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java @@ -0,0 +1,19 @@ +package smartpot.com.api.Crops.Validation; + +import java.util.List; + +public interface VCropI { + boolean isValid(); + + void validateId(String id); + + List getErrors(); + + void Reset(); + + void validateType(String type); + + void validateStatus(String validStatus); + + void validateUser(String user); +} diff --git a/src/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java index 074d2c7..e873281 100644 --- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java +++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java @@ -195,7 +195,7 @@ public ResponseEntity getUserById(@PathVariable @Parameter(description = "ID description = "Usuario no encontrado con el correo electrónico proporcionado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getUsersByEmail(@PathVariable @Parameter(description = "Correo electrónico del usuario", required = true) String email) { + public ResponseEntity getUsersByEmail(@Parameter(description = "Correo electrónico del usuario", required = true) @PathVariable String email) { try { return new ResponseEntity<>(serviceUser.getUserByEmail(email), HttpStatus.OK); } catch (Exception e) { @@ -235,7 +235,7 @@ public ResponseEntity getUsersByEmail(@PathVariable @Parameter(description = description = "No se encontraron usuarios con el nombre proporcionado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getUsersByName(@PathVariable @Parameter(description = "Nombre del usuario a buscar.", required = true) String name) { + public ResponseEntity getUsersByName(@Parameter(description = "Nombre del usuario a buscar.", required = true) @PathVariable String name) { try { return new ResponseEntity<>(serviceUser.getUsersByName(name), HttpStatus.OK); } catch (Exception e) { @@ -275,7 +275,7 @@ public ResponseEntity getUsersByName(@PathVariable @Parameter(description = " description = "No se encontraron usuarios con el apellido proporcionado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getUsersByLastname(@PathVariable @Parameter(description = "Apellido del usuario a buscar", required = true) String lastname) { + public ResponseEntity getUsersByLastname(@Parameter(description = "Apellido del usuario a buscar", required = true) @PathVariable String lastname) { try { return new ResponseEntity<>(serviceUser.getUsersByLastname(lastname), HttpStatus.OK); } catch (Exception e) { @@ -314,7 +314,7 @@ public ResponseEntity getUsersByLastname(@PathVariable @Parameter(description description = "No se encontraron usuarios con el rol proporcionado.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity getUsersByRole(@PathVariable @Parameter(description = "Rol del usuario a buscar", required = true) String role) { + public ResponseEntity getUsersByRole(@Parameter(description = "Rol del usuario a buscar", required = true) @PathVariable String role) { try { return new ResponseEntity<>(serviceUser.getUsersByRole(role), HttpStatus.OK); } catch (Exception e) { @@ -355,8 +355,8 @@ public ResponseEntity getUsersByRole(@PathVariable @Parameter(description = " content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) public ResponseEntity updateUser( - @PathVariable @Parameter(description = "ID único del usuario que se desea actualizar.", required = true) String id, - @RequestBody @Parameter(description = "Datos actualizados del usuario.") UserDTO updatedUser) { + @Parameter(description = "ID único del usuario que se desea actualizar.", required = true) @PathVariable String id, + @Parameter(description = "Datos actualizados del usuario.") @RequestBody UserDTO updatedUser) { try { return new ResponseEntity<>(serviceUser.UpdateUser(id, updatedUser), HttpStatus.OK); } catch (Exception e) { @@ -394,7 +394,7 @@ public ResponseEntity updateUser( description = "No se pudo eliminar el usuario.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - public ResponseEntity deleteUser(@PathVariable @Parameter(description = "ID único del usuario que se desea eliminar.", required = true) String id) { + public ResponseEntity deleteUser(@Parameter(description = "ID único del usuario que se desea eliminar.", required = true) @PathVariable String id) { try { return new ResponseEntity<>(new DeleteResponse("Se ha eliminado un recurso [" + serviceUser.DeleteUser(id) + "]"), HttpStatus.OK); } catch (Exception e) { diff --git a/src/main/java/smartpot/com/api/Users/Mapper/MUser.java b/src/main/java/smartpot/com/api/Users/Mapper/MUser.java index 97e20f6..99f3d0a 100644 --- a/src/main/java/smartpot/com/api/Users/Mapper/MUser.java +++ b/src/main/java/smartpot/com/api/Users/Mapper/MUser.java @@ -3,7 +3,6 @@ import org.bson.types.ObjectId; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import smartpot.com.api.Users.Model.DTO.UserDTO; import smartpot.com.api.Users.Model.Entity.User; @@ -14,8 +13,6 @@ @Mapper(componentModel = "spring") public interface MUser { - MUser INSTANCE = Mappers.getMapper(MUser.class); - @Mapping(source = "id", target = "id", qualifiedByName = "stringToObjectId") @Mapping(source = "password", target = "password", qualifiedByName = "encodePassword") @Mapping(source = "createAt", target = "createAt", qualifiedByName = "stringToDate") diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Repository/RUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Repository/RUser.java index 5f4f102..82b968e 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Repository/RUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Repository/RUser.java @@ -1,6 +1,5 @@ package smartpot.com.api.Users.Model.DAO.Repository; - import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; @@ -75,5 +74,14 @@ public interface RUser extends MongoRepository { @Query("{ 'role' : ?0 }") List findByRole(String role); + /** + * Verifica si existe un usuario con el correo electrónico proporcionado. + * + *

Este método devuelve {@code true} si existe un usuario con el correo electrónico especificado, + * y {@code false} en caso contrario.

+ * + * @param email El correo electrónico a verificar. + * @return {@code true} si existe un usuario con el correo electrónico proporcionado, {@code false} en caso contrario. + */ boolean existsByEmail(String email); } diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index 4b855b2..639f1b4 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -38,7 +38,7 @@ public class SUser implements SUserI { * Constructor que inyecta las dependencias del servicio. * * @param repositoryUser repositorio que maneja las operaciones de base de datos. - * @param mapperUser mapeador que convierte entidades User a UserDTO. + * @param mapperUser convertidor que convierte entidades User a UserDTO. * @param validatorUser validador que valida los datos de usuario. */ @Autowired @@ -48,28 +48,6 @@ public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { this.validatorUser = validatorUser; } - /** - * Obtiene todos los usuarios de la base de datos y los convierte a DTOs. - * * - * Este método consulta todos los usuarios almacenados en la base de datos utilizando el - * repositorio `repositoryUser`. Si la lista de usuarios está vacía, lanza una excepción. - * Los usuarios obtenidos se mapean a objetos `UserDTO` utilizando el objeto `mapperUser`. - * - * @return una lista de objetos {@link UserDTO} que representan a todos los usuarios. - * @throws Exception si no se encuentran usuarios en la base de datos. - * - * @see UserDTO - */ - @Override - public List getAllUsers() throws Exception { - return Optional.of(repositoryUser.findAll()) - .filter(users -> !users.isEmpty()) - .map(users -> users.stream() - .map(mapperUser::toDTO) - .collect(Collectors.toList())) - .orElseThrow(() -> new Exception("No existe ningún usuario")); - } - /** * Crea un nuevo usuario en la base de datos a partir de un objeto {@link UserDTO}. * * @@ -92,12 +70,12 @@ public UserDTO CreateUser(UserDTO userDTO) throws Exception { return Optional.of(userDTO) .filter(dto -> !repositoryUser.existsByEmail(dto.getEmail())) .map(ValidDTO -> { - validatorUser.validateName(userDTO.getName()); - validatorUser.validateLastname(userDTO.getLastname()); - validatorUser.validateEmail(userDTO.getEmail()); - validatorUser.validatePassword(userDTO.getPassword()); - validatorUser.validateRole(userDTO.getRole()); - if (validatorUser.isValid()) { + validatorUser.validateName(ValidDTO.getName()); + validatorUser.validateLastname(ValidDTO.getLastname()); + validatorUser.validateEmail(ValidDTO.getEmail()); + validatorUser.validatePassword(ValidDTO.getPassword()); + validatorUser.validateRole(ValidDTO.getRole()); + if (!validatorUser.isValid()) { throw new ValidationException(validatorUser.getErrors().toString()); } validatorUser.Reset(); @@ -114,6 +92,28 @@ public UserDTO CreateUser(UserDTO userDTO) throws Exception { .orElseThrow(() -> new Exception("El Usuario ya existe")); } + + /** + * Obtiene todos los usuarios de la base de datos y los convierte a DTOs. + * * + * Este método consulta todos los usuarios almacenados en la base de datos utilizando el + * repositorio `repositoryUser`. Si la lista de usuarios está vacía, lanza una excepción. + * Los usuarios obtenidos se mapean a objetos `UserDTO` utilizando el objeto `mapperUser`. + * + * @return una lista de objetos {@link UserDTO} que representan a todos los usuarios. + * @throws Exception si no se encuentran usuarios en la base de datos. + * @see UserDTO + */ + @Override + public List getAllUsers() throws Exception { + return Optional.of(repositoryUser.findAll()) + .filter(users -> !users.isEmpty()) + .map(users -> users.stream() + .map(mapperUser::toDTO) + .collect(Collectors.toList())) + .orElseThrow(() -> new Exception("No existe ningún usuario")); + } + /** * Obtiene un usuario de la base de datos a partir de su ID. * * @@ -134,8 +134,8 @@ public UserDTO CreateUser(UserDTO userDTO) throws Exception { public UserDTO getUserById(String id) throws Exception { return Optional.of(id) .map(ValidId -> { - validatorUser.validateId(id); - if (validatorUser.isValid()) { + validatorUser.validateId(ValidId); + if (!validatorUser.isValid()) { throw new ValidationException(validatorUser.getErrors().toString()); } validatorUser.Reset(); @@ -170,7 +170,7 @@ public UserDTO getUserByEmail(String email) throws Exception { return Optional.of(email) .map(ValidEmail -> { validatorUser.validateEmail(email); - if (validatorUser.isValid()) { + if (!validatorUser.isValid()) { throw new ValidationException(validatorUser.getErrors().toString()); } validatorUser.Reset(); @@ -205,7 +205,7 @@ public List getUsersByName(String name) throws Exception { return Optional.of(name) .map(ValidName -> { validatorUser.validateName(ValidName); - if (validatorUser.isValid()) { + if (!validatorUser.isValid()) { throw new ValidationException(validatorUser.getErrors().toString()); } validatorUser.Reset(); @@ -241,7 +241,7 @@ public List getUsersByLastname(String lastname) throws Exception { return Optional.of(lastname) .map(ValidLastname -> { validatorUser.validateLastname(ValidLastname); - if (validatorUser.isValid()) { + if (!validatorUser.isValid()) { throw new ValidationException(validatorUser.getErrors().toString()); } validatorUser.Reset(); @@ -277,7 +277,7 @@ public List getUsersByRole(String role) throws Exception { return Optional.of(role) .map(ValidRole -> { validatorUser.validateRole(ValidRole); - if (validatorUser.isValid()) { + if (!validatorUser.isValid()) { throw new ValidationException(validatorUser.getErrors().toString()); } validatorUser.Reset(); diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java index 6c50ece..d6b53f2 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java @@ -14,14 +14,6 @@ * correo electrónico y rol. */ public interface SUserI extends UserDetailsService { - /** - * Obtiene todos los usuarios registrados en el sistema. - * - * @return una lista de objetos {@link UserDTO} que representan a todos los usuarios. - * @throws Exception si ocurre un error al obtener los usuarios. - */ - List getAllUsers() throws Exception; - /** * Crea un nuevo usuario en el sistema. * @@ -31,6 +23,14 @@ public interface SUserI extends UserDetailsService { */ UserDTO CreateUser(UserDTO userDTO) throws Exception; + /** + * Obtiene todos los usuarios registrados en el sistema. + * + * @return una lista de objetos {@link UserDTO} que representan a todos los usuarios. + * @throws Exception si ocurre un error al obtener los usuarios. + */ + List getAllUsers() throws Exception; + /** * Obtiene un usuario por su ID. * diff --git a/src/main/java/smartpot/com/api/Users/Model/Entity/User.java b/src/main/java/smartpot/com/api/Users/Model/Entity/User.java index f5c9211..d4c9f68 100644 --- a/src/main/java/smartpot/com/api/Users/Model/Entity/User.java +++ b/src/main/java/smartpot/com/api/Users/Model/Entity/User.java @@ -8,8 +8,6 @@ import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import smartpot.com.api.Users.Model.DTO.UserDTO; import java.io.Serializable; import java.util.Date; @@ -77,20 +75,4 @@ public class User implements Serializable { @NotEmpty(message = "El rol no puede estar vacío") @Field("role") private Role role; - - /** - * Constructor que mapea los datos desde un {@link UserDTO}. - * Este constructor toma los datos de un DTO y los convierte a la entidad {@link User}. - * La contraseña se encripta utilizando BCrypt antes de ser almacenada. - * - * @param userDTO El objeto DTO con los datos del usuario. - */ - public User(UserDTO userDTO) { - this.name = userDTO.getName(); - this.lastname = userDTO.getLastname(); - this.email = userDTO.getEmail(); - BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - this.password = passwordEncoder.encode(userDTO.getPassword()); - this.role = Role.valueOf(userDTO.getRole()); - } } diff --git a/src/main/java/smartpot/com/api/Users/Validation/VUser.java b/src/main/java/smartpot/com/api/Users/Validation/VUser.java index 4c6202a..9b1f77d 100644 --- a/src/main/java/smartpot/com/api/Users/Validation/VUser.java +++ b/src/main/java/smartpot/com/api/Users/Validation/VUser.java @@ -45,7 +45,7 @@ public VUser() { */ @Override public boolean isValid() { - return !valid; + return valid; } /** From 7d76124518e526e3f7f8d9f09969076ab8db2558 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Sun, 29 Dec 2024 18:55:06 -0500 Subject: [PATCH 02/25] Temporary solution for migrating the use of exceptions, controllers and services --- .env.example | 1 + .../Controller/CommandController.java | 10 ++-- .../Commands/Model/DAO/Service/SCommand.java | 2 +- .../Commands/Model/DAO/Service/SCommandI.java | 2 +- .../api/Crops/Model/DAO/Service/SCrop.java | 4 +- .../com/api/Crops/Model/Entity/Crop.java | 6 ++- .../Records/Controller/HistoryController.java | 6 +-- .../Records/Model/DAO/Service/SHistory.java | 14 ++--- .../Records/Model/DAO/Service/SHistoryI.java | 4 +- .../api/Records/Model/DTO/CropRecordDTO.java | 4 +- .../com/api/SmartPotApiApplication.java | 25 +++++---- .../Crops/Controller/CropControllerTest.java | 52 +++++++++++++++++++ 12 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java diff --git a/.env.example b/.env.example index d300f5d..4722547 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ APP_NAME=app PORT=port TITLE=title +DESCRIPTION=description VERSION=version AUTHOR=author diff --git a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java index 7aa6842..b82bd1c 100644 --- a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java +++ b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java @@ -8,7 +8,7 @@ import smartpot.com.api.Commands.Model.DAO.Service.SCommandI; import smartpot.com.api.Commands.Model.Entity.Command; import smartpot.com.api.Crops.Model.DAO.Service.SCropI; -import smartpot.com.api.Crops.Model.Entity.Crop; +import smartpot.com.api.Crops.Model.DTO.CropDTO; import java.util.Date; import java.util.List; @@ -39,8 +39,8 @@ public Command getUserById(@PathVariable String id) { @PostMapping("/commandCreate/{cropId}") @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createCommand(@PathVariable String cropId, @RequestBody Command newCommand) { - Optional cropOpt = Optional.ofNullable(serviceCrop.getCropById(cropId)); + public ResponseEntity createCommand(@PathVariable String cropId, @RequestBody Command newCommand) throws Exception { + Optional cropOpt = Optional.ofNullable(serviceCrop.getCropById(cropId)); if (cropOpt.isPresent()) { newCommand.setCrop(new ObjectId(cropId)); newCommand.setDateCreated(new Date()); @@ -53,7 +53,7 @@ public ResponseEntity createCommand(@PathVariable String cropId, @Reque } @PutMapping("/{id}/ejecutar") - public ResponseEntity executeCommand(@PathVariable String id) { + public ResponseEntity executeCommand(@PathVariable String id) throws Exception { Command command = serviceCommand.getCommandById(id); if (command != null) { command.setStatus("EXECUTED"); @@ -77,7 +77,7 @@ public ResponseEntity deleteCommand(@PathVariable String id) { } @PutMapping("/Update/{id}") - public Command updateCommand(@PathVariable String id, @RequestBody Command updatedCommad) { + public Command updateCommand(@PathVariable String id, @RequestBody Command updatedCommad) throws Exception { return serviceCommand.updateCommand(id, updatedCommad); } diff --git a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java index a98cb6f..f89aa41 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java +++ b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java @@ -47,7 +47,7 @@ public Command createCommand(Command newCommand) { } @Override - public Command updateCommand(String id, Command upCommand) { + public Command updateCommand(String id, Command upCommand) throws Exception { if (!ObjectId.isValid(id)) { throw new ApiException(new ApiResponse( "El ID '" + id + "' no es válido. Asegúrate de que tiene 24 caracteres y solo incluye dígitos hexadecimales (0-9, a-f, A-F).", diff --git a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java index 6335d7c..2f74d19 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java +++ b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java @@ -11,7 +11,7 @@ public interface SCommandI { Command createCommand(Command newCommand); - Command updateCommand(String id, Command upCommand); + Command updateCommand(String id, Command upCommand) throws Exception; void deleteCommand(String id); } diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index 2bd4561..a41d614 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -244,9 +244,7 @@ public List getCropsByType(String type) throws Exception { */ @Override public long countCropsByUser(String id) throws Exception { - return Optional.of(getCropsByUser(id)) - .stream() - .count(); + return getCropsByUser(id).size(); } /** diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java index e6a7346..a0d4c97 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java @@ -8,7 +8,6 @@ import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; @@ -41,7 +40,10 @@ public class Crop implements Serializable { @Field("type") private Type type; - @DBRef + /** + * ! No se puede hacer referencia a los objetos, dado que obliga a usar la entidad completa, no solo el ObjectId. + */ + //@DBRef @NotNull(message = "El cultivo debe pertenecer a un usuario") @Field("user") private ObjectId user; diff --git a/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java b/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java index bfd643b..7cf7614 100644 --- a/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java +++ b/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java @@ -5,11 +5,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import smartpot.com.api.Exception.ApiResponse; -import smartpot.com.api.Responses.ErrorResponse; import smartpot.com.api.Records.Model.DAO.Service.SHistoryI; import smartpot.com.api.Records.Model.DTO.RecordDTO; import smartpot.com.api.Records.Model.Entity.DateRange; import smartpot.com.api.Records.Model.Entity.History; +import smartpot.com.api.Responses.ErrorResponse; import java.util.List; @@ -42,7 +42,7 @@ public List getAllHistories() { */ @PostMapping("/Create") @ResponseStatus(HttpStatus.CREATED) - public History createHistory(@RequestBody RecordDTO newHistory) { + public History createHistory(@RequestBody RecordDTO newHistory) throws Exception { return serviceHistory.Createhistory(newHistory); } @@ -53,7 +53,7 @@ public History createHistory(@RequestBody RecordDTO newHistory) { * @return Los históricos encontrado */ @GetMapping("/crop/{id}") - public List getByCrop(@PathVariable String id) { + public List getByCrop(@PathVariable String id) throws Exception { return serviceHistory.getByCrop(id); } diff --git a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistory.java b/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistory.java index c323f58..de4aea4 100644 --- a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistory.java +++ b/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistory.java @@ -9,7 +9,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import smartpot.com.api.Crops.Model.DAO.Service.SCropI; -import smartpot.com.api.Crops.Model.Entity.Crop; +import smartpot.com.api.Crops.Model.DTO.CropDTO; import smartpot.com.api.Exception.ApiException; import smartpot.com.api.Exception.ApiResponse; import smartpot.com.api.Records.Mapper.MRecords; @@ -212,8 +212,8 @@ public History getHistoryById(String id) { * @throws ApiException Si el cultivo con el ID proporcionado no se encuentra o si ocurre un error en la consulta. */ @Override - public List getByCrop(String cropId) { - return repositoryHistory.getHistoriesByCrop(serviceCrop.getCropById(cropId).getId()); + public List getByCrop(String cropId) throws Exception { + return repositoryHistory.getHistoriesByCrop(new ObjectId(serviceCrop.getCropById(cropId).getId())); } /** @@ -262,7 +262,7 @@ public List getHistoriesByCropAndDateBetween(String cropId, DateRange r @Override public List getByUser(String id) throws Exception { List records = new ArrayList<>(); - List crops = serviceCrop.getCropsByUser(id); + List crops = serviceCrop.getCropsByUser(id); // Verificar si el usuario tiene cultivos if (crops.isEmpty()) { throw new ApiException(new ApiResponse( @@ -270,8 +270,8 @@ public List getByUser(String id) throws Exception { HttpStatus.NOT_FOUND.value() )); } - for (Crop crop : crops) { - List histories = repositoryHistory.getHistoriesByCrop(crop.getId()); + for (CropDTO crop : crops) { + List histories = repositoryHistory.getHistoriesByCrop(new ObjectId(crop.getId())); for (History history : histories) { records.add(new CropRecordDTO(crop, history)); @@ -288,7 +288,7 @@ public List getByUser(String id) throws Exception { * @return El histórico creado */ @Override - public History Createhistory(RecordDTO recordDTO) { + public History Createhistory(RecordDTO recordDTO) throws Exception { ValidationMesuares(recordDTO.getMeasures()); serviceCrop.getCropById(recordDTO.getCrop()); History history = MRecords.INSTANCE.toEntity(recordDTO); diff --git a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistoryI.java b/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistoryI.java index 2f20e0e..d503274 100644 --- a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistoryI.java +++ b/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistoryI.java @@ -14,13 +14,13 @@ public interface SHistoryI { History getHistoryById(String id); - List getByCrop(String cropId); + List getByCrop(String cropId) throws Exception; List getHistoriesByCropAndDateBetween(String cropId, DateRange ranges); List getByUser(String id) throws Exception; - History Createhistory(RecordDTO recordDTO); + History Createhistory(RecordDTO recordDTO) throws Exception; History updatedHistory(History existingHistory, RecordDTO updateHistory); diff --git a/src/main/java/smartpot/com/api/Records/Model/DTO/CropRecordDTO.java b/src/main/java/smartpot/com/api/Records/Model/DTO/CropRecordDTO.java index a6d7983..bb803a5 100644 --- a/src/main/java/smartpot/com/api/Records/Model/DTO/CropRecordDTO.java +++ b/src/main/java/smartpot/com/api/Records/Model/DTO/CropRecordDTO.java @@ -1,7 +1,7 @@ package smartpot.com.api.Records.Model.DTO; import lombok.Data; -import smartpot.com.api.Crops.Model.Entity.Crop; +import smartpot.com.api.Crops.Model.DTO.CropDTO; import smartpot.com.api.Records.Model.Entity.History; import smartpot.com.api.Records.Model.Entity.Measures; @@ -14,7 +14,7 @@ public class CropRecordDTO { private String date; private MeasuresDTO measures; - public CropRecordDTO(Crop crop, History history) { + public CropRecordDTO(CropDTO crop, History history) { this.crop = String.valueOf(crop.getId()); this.status = String.valueOf(crop.getStatus()); this.type = String.valueOf(crop.getType()); diff --git a/src/main/java/smartpot/com/api/SmartPotApiApplication.java b/src/main/java/smartpot/com/api/SmartPotApiApplication.java index f909b0a..aebb0ee 100644 --- a/src/main/java/smartpot/com/api/SmartPotApiApplication.java +++ b/src/main/java/smartpot/com/api/SmartPotApiApplication.java @@ -1,29 +1,36 @@ package smartpot.com.api; import io.github.cdimascio.dotenv.Dotenv; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication @EnableMongoRepositories(basePackages = "smartpot.com.api") +@Slf4j public class SmartPotApiApplication { public static void main(String[] args) { - //loadEnv(); + loadEnv(); SpringApplication.run(SmartPotApiApplication.class, args); } private static void loadEnv() { - Dotenv dotenv = Dotenv.load(); + try { + Dotenv dotenv = Dotenv.load(); - dotenv.entries().forEach(entry -> { - String key = entry.getKey(); - String value = entry.getValue(); + dotenv.entries().forEach(entry -> { + String key = entry.getKey(); + String value = entry.getValue(); - if (value != null) { - System.setProperty(key, value); - } - }); + if (value != null) { + System.setProperty(key, value); + } + }); + } catch (Exception e) { + log.warn("No se pudo cargar el archivo .env. Se continuará con las variables de entorno ya presentes."); + log.debug("Detalles del error: ", e); + } } } diff --git a/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java b/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java new file mode 100644 index 0000000..32b8a2b --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java @@ -0,0 +1,52 @@ +package smartpot.com.api.Crops.Controller; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CropControllerTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void createCrop() { + } + + @Test + void getAllCrops() { + } + + @Test + void getCropById() { + } + + @Test + void getCropsByStatus() { + } + + @Test + void getCropsByType() { + } + + @Test + void getCropByUser() { + } + + @Test + void countCropsByUser() { + } + + @Test + void updateCrop() { + } + + @Test + void deleteCrop() { + } +} \ No newline at end of file From fa856fb73556978c7d0464f453f3cb0639d1fa13 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Sun, 29 Dec 2024 23:51:50 -0500 Subject: [PATCH 03/25] creation of structure for tests --- .../com/api/Crops/Model/DTO/CropDTO.java | 8 +- .../Crops/Controller/CropControllerTest.java | 148 ++++++++++++++++-- .../com/api/Crops/Mapper/MCropTest.java | 34 ++++ .../Crops/Model/DAO/Repository/RCropTest.java | 30 ++++ .../Crops/Model/DAO/Service/SCropITest.java | 54 +++++++ .../Crops/Model/DAO/Service/SCropTest.java | 90 +++++++++++ .../com/api/Crops/Model/DTO/CropDTOTest.java | 66 ++++++++ .../com/api/Crops/Model/Entity/CropTest.java | 60 +++++++ .../api/Crops/Model/Entity/StatusTest.java | 70 +++++++++ .../com/api/Crops/Model/Entity/TypeTest.java | 70 +++++++++ .../com/api/Crops/Validation/VCropITest.java | 45 ++++++ .../com/api/Crops/Validation/VCropTest.java | 46 ++++++ 12 files changed, 706 insertions(+), 15 deletions(-) create mode 100644 src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropITest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java create mode 100644 src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java diff --git a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java index 6c68373..ea02049 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java @@ -1,10 +1,7 @@ package smartpot.com.api.Crops.Model.DTO; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; +import lombok.*; /** @@ -14,9 +11,8 @@ * o manipulación en las operaciones CRUD de cultivos. */ @Data +@AllArgsConstructor @RequiredArgsConstructor -@Getter -@Setter public class CropDTO { @Schema(description = "ID único del cultivo, generado automáticamente por la base de datos.", diff --git a/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java b/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java index 32b8a2b..544ed0a 100644 --- a/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java +++ b/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java @@ -1,52 +1,182 @@ package smartpot.com.api.Crops.Controller; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import smartpot.com.api.Crops.Model.DAO.Service.SCropI; +import smartpot.com.api.Crops.Model.DTO.CropDTO; +import smartpot.com.api.Crops.Model.Entity.Status; +import smartpot.com.api.Crops.Model.Entity.Type; +import java.util.Arrays; +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(CropController.class) +@ComponentScan(basePackages = "smartpot.com.api.Crops") class CropControllerTest { + @Autowired + private MockMvc mockMvc; + + @Mock + private SCropI serviceCrop; + + @Autowired + private ObjectMapper objectMapper; + @BeforeEach void setUp() { + // Aquí se puede inicializar cualquier cosa antes de cada prueba, si es necesario } @AfterEach void tearDown() { + // Aquí se puede limpiar lo que se necesite después de cada prueba, si es necesario } + // Test para POST /Crops/Create @Test - void createCrop() { + void createCrop() throws Exception { + // Usamos un valor válido de los enums Status y Type + CropDTO newCrop = new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"); + + // Mockeamos la respuesta del servicio + when(serviceCrop.createCrop(any(CropDTO.class))).thenReturn(newCrop); + + mockMvc.perform(post("/Crops/Create") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newCrop))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value("60b63b8f3e111f8d44d45e72")) + .andExpect(jsonPath("$.status").value("Perfect_plant")) + .andExpect(jsonPath("$.type").value("TOMATO")) + .andExpect(jsonPath("$.user").value("676ae2a9b909de5f9607fcb6")); } + // Test para GET /Crops/All @Test - void getAllCrops() { + void getAllCrops() throws Exception { + List crops = Arrays.asList( + new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"), + new CropDTO("60b63b8f3e111f8d44d45e73", Status.Healthy_state.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb7") + ); + when(serviceCrop.getAllCrops()).thenReturn(crops); + + mockMvc.perform(get("/Crops/All")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value("60b63b8f3e111f8d44d45e72")) + .andExpect(jsonPath("$[1].id").value("60b63b8f3e111f8d44d45e73")) + .andExpect(jsonPath("$[0].status").value("Perfect_plant")) + .andExpect(jsonPath("$[1].status").value("Healthy_state")) + .andExpect(jsonPath("$[0].type").value("TOMATO")) + .andExpect(jsonPath("$[1].type").value("LETTUCE")); } + // Test para GET /Crops/id/{id} @Test - void getCropById() { + void getCropById() throws Exception { + CropDTO crop = new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"); + when(serviceCrop.getCropById("60b63b8f3e111f8d44d45e72")).thenReturn(crop); + + mockMvc.perform(get("/Crops/id/{id}", "60b63b8f3e111f8d44d45e72")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value("60b63b8f3e111f8d44d45e72")) + .andExpect(jsonPath("$.status").value("Perfect_plant")) + .andExpect(jsonPath("$.type").value("TOMATO")) + .andExpect(jsonPath("$.user").value("676ae2a9b909de5f9607fcb6")); } + // Test para GET /Crops/status/{status} @Test - void getCropsByStatus() { + void getCropsByStatus() throws Exception { + List crops = Arrays.asList( + new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"), + new CropDTO("60b63b8f3e111f8d44d45e73", Status.Perfect_plant.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb7") + ); + when(serviceCrop.getCropsByStatus(Status.Perfect_plant.name())).thenReturn(crops); + + mockMvc.perform(get("/Crops/status/{status}", Status.Perfect_plant.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].status").value("Perfect_plant")) + .andExpect(jsonPath("$[1].status").value("Perfect_plant")); } + // Test para GET /Crops/type/{type} @Test - void getCropsByType() { + void getCropsByType() throws Exception { + List crops = Arrays.asList( + new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"), + new CropDTO("60b63b8f3e111f8d44d45e73", Status.Healthy_state.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb7") + ); + when(serviceCrop.getCropsByType(Type.TOMATO.name())).thenReturn(crops); + + mockMvc.perform(get("/Crops/type/{type}", Type.TOMATO.name())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].type").value("TOMATO")) + .andExpect(jsonPath("$[1].type").value("LETTUCE")); } + // Test para GET /Crops/user/{id} @Test - void getCropByUser() { + void getCropByUser() throws Exception { + List crops = Arrays.asList( + new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6") + ); + when(serviceCrop.getCropsByUser("676ae2a9b909de5f9607fcb6")).thenReturn(crops); + + mockMvc.perform(get("/Crops/user/{id}", "676ae2a9b909de5f9607fcb6")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].user").value("676ae2a9b909de5f9607fcb6")); } + // Test para GET /Crops/count/{id} @Test - void countCropsByUser() { + void countCropsByUser() throws Exception { + when(serviceCrop.countCropsByUser("676ae2a9b909de5f9607fcb6")).thenReturn(3L); + + mockMvc.perform(get("/Crops/count/{id}", "676ae2a9b909de5f9607fcb6")) + .andExpect(status().isOk()) + .andExpect(content().string("3")); } + // Test para PUT /Crops/Update/{id} @Test - void updateCrop() { + void updateCrop() throws Exception { + CropDTO updatedCrop = new CropDTO("60b63b8f3e111f8d44d45e72", Status.Healthy_state.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb6"); + when(serviceCrop.updatedCrop(any(String.class), any(CropDTO.class))).thenReturn(updatedCrop); + + mockMvc.perform(put("/Crops/Update/{id}", "60b63b8f3e111f8d44d45e72") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updatedCrop))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value("60b63b8f3e111f8d44d45e72")) + .andExpect(jsonPath("$.status").value("Healthy_state")) + .andExpect(jsonPath("$.type").value("LETTUCE")) + .andExpect(jsonPath("$.user").value("676ae2a9b909de5f9607fcb6")); } + // Test para DELETE /Crops/Delete/{id} @Test - void deleteCrop() { + void deleteCrop() throws Exception { + doNothing().when(serviceCrop).deleteCrop("60b63b8f3e111f8d44d45e72"); + + mockMvc.perform(delete("/Crops/Delete/{id}", "60b63b8f3e111f8d44d45e72")) + .andExpect(status().isNoContent()); } } \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java b/src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java new file mode 100644 index 0000000..7a3db4d --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java @@ -0,0 +1,34 @@ +package smartpot.com.api.Crops.Mapper; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class MCropTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void toEntity() { + } + + @Test + void toDTO() { + } + + @Test + void objectIdToString() { + } + + @Test + void stringToObjectId() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java b/src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java new file mode 100644 index 0000000..d4a8bbe --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java @@ -0,0 +1,30 @@ +package smartpot.com.api.Crops.Model.DAO.Repository; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RCropTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void findByType() { + } + + @Test + void findByStatus() { + } + + @Test + void findByUser() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropITest.java b/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropITest.java new file mode 100644 index 0000000..7d67964 --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropITest.java @@ -0,0 +1,54 @@ +package smartpot.com.api.Crops.Model.DAO.Service; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SCropITest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void getAllCrops() { + } + + @Test + void getCropById() { + } + + @Test + void getCropsByUser() { + } + + @Test + void getCropsByType() { + } + + @Test + void countCropsByUser() { + } + + @Test + void getCropsByStatus() { + } + + @Test + void createCrop() { + } + + @Test + void updatedCrop() { + } + + @Test + void deleteCrop() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java b/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java new file mode 100644 index 0000000..77f70f0 --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java @@ -0,0 +1,90 @@ +package smartpot.com.api.Crops.Model.DAO.Service; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SCropTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void createCrop() { + } + + @Test + void getAllCrops() { + } + + @Test + void getCropById() { + } + + @Test + void getCropsByUser() { + } + + @Test + void getCropsByType() { + } + + @Test + void countCropsByUser() { + } + + @Test + void getCropsByStatus() { + } + + @Test + void updatedCrop() { + } + + @Test + void deleteCrop() { + } + + @Test + void getRepositoryCrop() { + } + + @Test + void getServiceUser() { + } + + @Test + void getMapperCrop() { + } + + @Test + void getValidatorCrop() { + } + + @Test + void testEquals() { + } + + @Test + void canEqual() { + } + + @Test + void testHashCode() { + } + + @Test + void testToString() { + } + + @Test + void builder() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java b/src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java new file mode 100644 index 0000000..752f743 --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java @@ -0,0 +1,66 @@ +package smartpot.com.api.Crops.Model.DTO; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CropDTOTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void testEquals() { + } + + @Test + void canEqual() { + } + + @Test + void testHashCode() { + } + + @Test + void testToString() { + } + + @Test + void getId() { + } + + @Test + void getStatus() { + } + + @Test + void getType() { + } + + @Test + void getUser() { + } + + @Test + void setId() { + } + + @Test + void setStatus() { + } + + @Test + void setType() { + } + + @Test + void setUser() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java b/src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java new file mode 100644 index 0000000..f13c486 --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java @@ -0,0 +1,60 @@ +package smartpot.com.api.Crops.Model.Entity; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CropTest { + + @Test + void getId() { + } + + @Test + void getStatus() { + } + + @Test + void getType() { + } + + @Test + void getUser() { + } + + @Test + void setId() { + } + + @Test + void setStatus() { + } + + @Test + void setType() { + } + + @Test + void setUser() { + } + + @Test + void testEquals() { + } + + @Test + void canEqual() { + } + + @Test + void testHashCode() { + } + + @Test + void testToString() { + } + + @Test + void builder() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java b/src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java new file mode 100644 index 0000000..fc86bf4 --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java @@ -0,0 +1,70 @@ +package smartpot.com.api.Crops.Model.Entity; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class StatusTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void name() { + } + + @Test + void ordinal() { + } + + @Test + void testToString() { + } + + @Test + void testEquals() { + } + + @Test + void testHashCode() { + } + + @Test + void testClone() { + } + + @Test + void compareTo() { + } + + @Test + void getDeclaringClass() { + } + + @Test + void describeConstable() { + } + + @Test + void valueOf() { + } + + @Test + void testFinalize() { + } + + @Test + void values() { + } + + @Test + void testValueOf() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java b/src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java new file mode 100644 index 0000000..71d3a2c --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java @@ -0,0 +1,70 @@ +package smartpot.com.api.Crops.Model.Entity; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TypeTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void name() { + } + + @Test + void ordinal() { + } + + @Test + void testToString() { + } + + @Test + void testEquals() { + } + + @Test + void testHashCode() { + } + + @Test + void testClone() { + } + + @Test + void compareTo() { + } + + @Test + void getDeclaringClass() { + } + + @Test + void describeConstable() { + } + + @Test + void valueOf() { + } + + @Test + void testFinalize() { + } + + @Test + void values() { + } + + @Test + void testValueOf() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java b/src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java new file mode 100644 index 0000000..e42355e --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java @@ -0,0 +1,45 @@ +package smartpot.com.api.Crops.Validation; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +class VCropITest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void isValid() { + } + + @Test + void validateId() { + } + + @Test + void getErrors() { + } + + @Test + void reset() { + } + + @Test + void validateType() { + } + + @Test + void validateStatus() { + } + + @Test + void validateUser() { + } +} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java b/src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java new file mode 100644 index 0000000..0ccbdce --- /dev/null +++ b/src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java @@ -0,0 +1,46 @@ +package smartpot.com.api.Crops.Validation; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class VCropTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void isValid() { + } + + @Test + void validateId() { + } + + @Test + void getErrors() { + } + + @Test + void reset() { + } + + @Test + void validateType() { + } + + @Test + void validateStatus() { + } + + @Test + void validateUser() { + } +} \ No newline at end of file From 30970a86961914b6c5df1c3335bd9c70345d13d1 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Mon, 30 Dec 2024 11:47:43 -0500 Subject: [PATCH 04/25] Framework for unit testing --- README.md | 7 +- .../Crops/Controller/CropControllerTest.java | 182 ------------------ .../com/api/Crops/Mapper/MCropTest.java | 34 ---- .../Crops/Model/DAO/Repository/RCropTest.java | 30 --- .../Crops/Model/DAO/Service/SCropTest.java | 90 --------- .../com/api/Crops/Model/DTO/CropDTOTest.java | 66 ------- .../com/api/Crops/Model/Entity/CropTest.java | 60 ------ .../api/Crops/Model/Entity/StatusTest.java | 70 ------- .../com/api/Crops/Model/Entity/TypeTest.java | 70 ------- .../com/api/Crops/Validation/VCropITest.java | 45 ----- .../com/api/Crops/Validation/VCropTest.java | 46 ----- .../com/api/SmartPotApiApplicationTests.java | 13 -- .../Controller/UserControllerTest.java} | 22 +-- 13 files changed, 17 insertions(+), 718 deletions(-) delete mode 100644 src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java delete mode 100644 src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java delete mode 100644 src/test/java/smartpot/com/api/SmartPotApiApplicationTests.java rename src/test/java/smartpot/com/api/{Crops/Model/DAO/Service/SCropITest.java => Users/Controller/UserControllerTest.java} (56%) diff --git a/README.md b/README.md index 22af125..e393c21 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # SmartPot-API ## Deployment -[![Docker Image CI](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/docker-image.yml/badge.svg)](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/docker-image.yml) + +[![General CI Pipeline](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/ci-pipeline.yml/badge.svg)](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/ci-pipeline.yml) + +[![Checkout Code](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/checkout.yml/badge.svg)](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/checkout.yml) + + ### 1. Compilación de la Aplicación diff --git a/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java b/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java deleted file mode 100644 index 544ed0a..0000000 --- a/src/test/java/smartpot/com/api/Crops/Controller/CropControllerTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package smartpot.com.api.Crops.Controller; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import smartpot.com.api.Crops.Model.DAO.Service.SCropI; -import smartpot.com.api.Crops.Model.DTO.CropDTO; -import smartpot.com.api.Crops.Model.Entity.Status; -import smartpot.com.api.Crops.Model.Entity.Type; - -import java.util.Arrays; -import java.util.List; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static org.mockito.Mockito.*; - -@ExtendWith(SpringExtension.class) -@WebMvcTest(CropController.class) -@ComponentScan(basePackages = "smartpot.com.api.Crops") -class CropControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Mock - private SCropI serviceCrop; - - @Autowired - private ObjectMapper objectMapper; - - @BeforeEach - void setUp() { - // Aquí se puede inicializar cualquier cosa antes de cada prueba, si es necesario - } - - @AfterEach - void tearDown() { - // Aquí se puede limpiar lo que se necesite después de cada prueba, si es necesario - } - - // Test para POST /Crops/Create - @Test - void createCrop() throws Exception { - // Usamos un valor válido de los enums Status y Type - CropDTO newCrop = new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"); - - // Mockeamos la respuesta del servicio - when(serviceCrop.createCrop(any(CropDTO.class))).thenReturn(newCrop); - - mockMvc.perform(post("/Crops/Create") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(newCrop))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id").value("60b63b8f3e111f8d44d45e72")) - .andExpect(jsonPath("$.status").value("Perfect_plant")) - .andExpect(jsonPath("$.type").value("TOMATO")) - .andExpect(jsonPath("$.user").value("676ae2a9b909de5f9607fcb6")); - } - - // Test para GET /Crops/All - @Test - void getAllCrops() throws Exception { - List crops = Arrays.asList( - new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"), - new CropDTO("60b63b8f3e111f8d44d45e73", Status.Healthy_state.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb7") - ); - when(serviceCrop.getAllCrops()).thenReturn(crops); - - mockMvc.perform(get("/Crops/All")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].id").value("60b63b8f3e111f8d44d45e72")) - .andExpect(jsonPath("$[1].id").value("60b63b8f3e111f8d44d45e73")) - .andExpect(jsonPath("$[0].status").value("Perfect_plant")) - .andExpect(jsonPath("$[1].status").value("Healthy_state")) - .andExpect(jsonPath("$[0].type").value("TOMATO")) - .andExpect(jsonPath("$[1].type").value("LETTUCE")); - } - - // Test para GET /Crops/id/{id} - @Test - void getCropById() throws Exception { - CropDTO crop = new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"); - when(serviceCrop.getCropById("60b63b8f3e111f8d44d45e72")).thenReturn(crop); - - mockMvc.perform(get("/Crops/id/{id}", "60b63b8f3e111f8d44d45e72")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value("60b63b8f3e111f8d44d45e72")) - .andExpect(jsonPath("$.status").value("Perfect_plant")) - .andExpect(jsonPath("$.type").value("TOMATO")) - .andExpect(jsonPath("$.user").value("676ae2a9b909de5f9607fcb6")); - } - - // Test para GET /Crops/status/{status} - @Test - void getCropsByStatus() throws Exception { - List crops = Arrays.asList( - new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"), - new CropDTO("60b63b8f3e111f8d44d45e73", Status.Perfect_plant.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb7") - ); - when(serviceCrop.getCropsByStatus(Status.Perfect_plant.name())).thenReturn(crops); - - mockMvc.perform(get("/Crops/status/{status}", Status.Perfect_plant.name())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].status").value("Perfect_plant")) - .andExpect(jsonPath("$[1].status").value("Perfect_plant")); - } - - // Test para GET /Crops/type/{type} - @Test - void getCropsByType() throws Exception { - List crops = Arrays.asList( - new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6"), - new CropDTO("60b63b8f3e111f8d44d45e73", Status.Healthy_state.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb7") - ); - when(serviceCrop.getCropsByType(Type.TOMATO.name())).thenReturn(crops); - - mockMvc.perform(get("/Crops/type/{type}", Type.TOMATO.name())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].type").value("TOMATO")) - .andExpect(jsonPath("$[1].type").value("LETTUCE")); - } - - // Test para GET /Crops/user/{id} - @Test - void getCropByUser() throws Exception { - List crops = Arrays.asList( - new CropDTO("60b63b8f3e111f8d44d45e72", Status.Perfect_plant.name(), Type.TOMATO.name(), "676ae2a9b909de5f9607fcb6") - ); - when(serviceCrop.getCropsByUser("676ae2a9b909de5f9607fcb6")).thenReturn(crops); - - mockMvc.perform(get("/Crops/user/{id}", "676ae2a9b909de5f9607fcb6")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].user").value("676ae2a9b909de5f9607fcb6")); - } - - // Test para GET /Crops/count/{id} - @Test - void countCropsByUser() throws Exception { - when(serviceCrop.countCropsByUser("676ae2a9b909de5f9607fcb6")).thenReturn(3L); - - mockMvc.perform(get("/Crops/count/{id}", "676ae2a9b909de5f9607fcb6")) - .andExpect(status().isOk()) - .andExpect(content().string("3")); - } - - // Test para PUT /Crops/Update/{id} - @Test - void updateCrop() throws Exception { - CropDTO updatedCrop = new CropDTO("60b63b8f3e111f8d44d45e72", Status.Healthy_state.name(), Type.LETTUCE.name(), "676ae2a9b909de5f9607fcb6"); - when(serviceCrop.updatedCrop(any(String.class), any(CropDTO.class))).thenReturn(updatedCrop); - - mockMvc.perform(put("/Crops/Update/{id}", "60b63b8f3e111f8d44d45e72") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(updatedCrop))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value("60b63b8f3e111f8d44d45e72")) - .andExpect(jsonPath("$.status").value("Healthy_state")) - .andExpect(jsonPath("$.type").value("LETTUCE")) - .andExpect(jsonPath("$.user").value("676ae2a9b909de5f9607fcb6")); - } - - // Test para DELETE /Crops/Delete/{id} - @Test - void deleteCrop() throws Exception { - doNothing().when(serviceCrop).deleteCrop("60b63b8f3e111f8d44d45e72"); - - mockMvc.perform(delete("/Crops/Delete/{id}", "60b63b8f3e111f8d44d45e72")) - .andExpect(status().isNoContent()); - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java b/src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java deleted file mode 100644 index 7a3db4d..0000000 --- a/src/test/java/smartpot/com/api/Crops/Mapper/MCropTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package smartpot.com.api.Crops.Mapper; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class MCropTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void toEntity() { - } - - @Test - void toDTO() { - } - - @Test - void objectIdToString() { - } - - @Test - void stringToObjectId() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java b/src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java deleted file mode 100644 index d4a8bbe..0000000 --- a/src/test/java/smartpot/com/api/Crops/Model/DAO/Repository/RCropTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package smartpot.com.api.Crops.Model.DAO.Repository; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class RCropTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void findByType() { - } - - @Test - void findByStatus() { - } - - @Test - void findByUser() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java b/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java deleted file mode 100644 index 77f70f0..0000000 --- a/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package smartpot.com.api.Crops.Model.DAO.Service; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class SCropTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void createCrop() { - } - - @Test - void getAllCrops() { - } - - @Test - void getCropById() { - } - - @Test - void getCropsByUser() { - } - - @Test - void getCropsByType() { - } - - @Test - void countCropsByUser() { - } - - @Test - void getCropsByStatus() { - } - - @Test - void updatedCrop() { - } - - @Test - void deleteCrop() { - } - - @Test - void getRepositoryCrop() { - } - - @Test - void getServiceUser() { - } - - @Test - void getMapperCrop() { - } - - @Test - void getValidatorCrop() { - } - - @Test - void testEquals() { - } - - @Test - void canEqual() { - } - - @Test - void testHashCode() { - } - - @Test - void testToString() { - } - - @Test - void builder() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java b/src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java deleted file mode 100644 index 752f743..0000000 --- a/src/test/java/smartpot/com/api/Crops/Model/DTO/CropDTOTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package smartpot.com.api.Crops.Model.DTO; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class CropDTOTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void testEquals() { - } - - @Test - void canEqual() { - } - - @Test - void testHashCode() { - } - - @Test - void testToString() { - } - - @Test - void getId() { - } - - @Test - void getStatus() { - } - - @Test - void getType() { - } - - @Test - void getUser() { - } - - @Test - void setId() { - } - - @Test - void setStatus() { - } - - @Test - void setType() { - } - - @Test - void setUser() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java b/src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java deleted file mode 100644 index f13c486..0000000 --- a/src/test/java/smartpot/com/api/Crops/Model/Entity/CropTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package smartpot.com.api.Crops.Model.Entity; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class CropTest { - - @Test - void getId() { - } - - @Test - void getStatus() { - } - - @Test - void getType() { - } - - @Test - void getUser() { - } - - @Test - void setId() { - } - - @Test - void setStatus() { - } - - @Test - void setType() { - } - - @Test - void setUser() { - } - - @Test - void testEquals() { - } - - @Test - void canEqual() { - } - - @Test - void testHashCode() { - } - - @Test - void testToString() { - } - - @Test - void builder() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java b/src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java deleted file mode 100644 index fc86bf4..0000000 --- a/src/test/java/smartpot/com/api/Crops/Model/Entity/StatusTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package smartpot.com.api.Crops.Model.Entity; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class StatusTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void name() { - } - - @Test - void ordinal() { - } - - @Test - void testToString() { - } - - @Test - void testEquals() { - } - - @Test - void testHashCode() { - } - - @Test - void testClone() { - } - - @Test - void compareTo() { - } - - @Test - void getDeclaringClass() { - } - - @Test - void describeConstable() { - } - - @Test - void valueOf() { - } - - @Test - void testFinalize() { - } - - @Test - void values() { - } - - @Test - void testValueOf() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java b/src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java deleted file mode 100644 index 71d3a2c..0000000 --- a/src/test/java/smartpot/com/api/Crops/Model/Entity/TypeTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package smartpot.com.api.Crops.Model.Entity; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class TypeTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void name() { - } - - @Test - void ordinal() { - } - - @Test - void testToString() { - } - - @Test - void testEquals() { - } - - @Test - void testHashCode() { - } - - @Test - void testClone() { - } - - @Test - void compareTo() { - } - - @Test - void getDeclaringClass() { - } - - @Test - void describeConstable() { - } - - @Test - void valueOf() { - } - - @Test - void testFinalize() { - } - - @Test - void values() { - } - - @Test - void testValueOf() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java b/src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java deleted file mode 100644 index e42355e..0000000 --- a/src/test/java/smartpot/com/api/Crops/Validation/VCropITest.java +++ /dev/null @@ -1,45 +0,0 @@ -package smartpot.com.api.Crops.Validation; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; -class VCropITest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void isValid() { - } - - @Test - void validateId() { - } - - @Test - void getErrors() { - } - - @Test - void reset() { - } - - @Test - void validateType() { - } - - @Test - void validateStatus() { - } - - @Test - void validateUser() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java b/src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java deleted file mode 100644 index 0ccbdce..0000000 --- a/src/test/java/smartpot/com/api/Crops/Validation/VCropTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package smartpot.com.api.Crops.Validation; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class VCropTest { - - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { - } - - @Test - void isValid() { - } - - @Test - void validateId() { - } - - @Test - void getErrors() { - } - - @Test - void reset() { - } - - @Test - void validateType() { - } - - @Test - void validateStatus() { - } - - @Test - void validateUser() { - } -} \ No newline at end of file diff --git a/src/test/java/smartpot/com/api/SmartPotApiApplicationTests.java b/src/test/java/smartpot/com/api/SmartPotApiApplicationTests.java deleted file mode 100644 index 7b75231..0000000 --- a/src/test/java/smartpot/com/api/SmartPotApiApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package smartpot.com.api; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SmartPotApiApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropITest.java b/src/test/java/smartpot/com/api/Users/Controller/UserControllerTest.java similarity index 56% rename from src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropITest.java rename to src/test/java/smartpot/com/api/Users/Controller/UserControllerTest.java index 7d67964..c1afed7 100644 --- a/src/test/java/smartpot/com/api/Crops/Model/DAO/Service/SCropITest.java +++ b/src/test/java/smartpot/com/api/Users/Controller/UserControllerTest.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Crops.Model.DAO.Service; +package smartpot.com.api.Users.Controller; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.*; -class SCropITest { +class UserControllerTest { @BeforeEach void setUp() { @@ -17,38 +17,38 @@ void tearDown() { } @Test - void getAllCrops() { + void createUser() { } @Test - void getCropById() { + void getAllUsers() { } @Test - void getCropsByUser() { + void getUserById() { } @Test - void getCropsByType() { + void getUsersByEmail() { } @Test - void countCropsByUser() { + void getUsersByName() { } @Test - void getCropsByStatus() { + void getUsersByLastname() { } @Test - void createCrop() { + void getUsersByRole() { } @Test - void updatedCrop() { + void updateUser() { } @Test - void deleteCrop() { + void deleteUser() { } } \ No newline at end of file From cb9d57ccbb748af3b73884c40cc4ca3d7ca0b137 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Wed, 1 Jan 2025 15:53:26 -0500 Subject: [PATCH 05/25] Validations for crop service --- .../api/Crops/Controller/CropController.java | 78 ++++++++++++++++++- .../api/Crops/Model/DAO/Service/SCrop.java | 74 ++++++++++++++---- .../api/Crops/Model/DAO/Service/SCropI.java | 7 +- .../com/api/Crops/Model/Entity/Status.java | 17 +++- .../com/api/Crops/Model/Entity/Type.java | 12 ++- .../com/api/Crops/Validation/VCrop.java | 44 +++++++++-- .../com/api/Crops/Validation/VCropI.java | 2 - .../api/Users/Controller/UserController.java | 41 +++++++++- .../api/Users/Model/DAO/Service/SUser.java | 18 +++++ .../api/Users/Model/DAO/Service/SUserI.java | 9 +++ .../com/api/Users/Model/Entity/Role.java | 17 +++- .../com/api/Users/Validation/VUser.java | 36 +++------ .../com/api/SmartPotApiApplicationTest.java | 12 +++ src/test/resources/application.properties | 44 +++++++++++ 14 files changed, 357 insertions(+), 54 deletions(-) create mode 100644 src/test/java/smartpot/com/api/SmartPotApiApplicationTest.java create mode 100644 src/test/resources/application.properties diff --git a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java index 3b9d825..50be387 100644 --- a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java +++ b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java @@ -97,7 +97,7 @@ public ResponseEntity createCrop(@Parameter(description = "Datos del nuevo cu @GetMapping("/All") @Operation(summary = "Obtener todos los cultivos", description = "Recupera todos los cultivos registrados en el sistema. " - + "En caso de no haber cultivos, se devolverá una lista vacía.", + + "En caso de no haber cultivos, se devolverá una excepción.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(description = "Cultivos encontrados", responseCode = "200", @@ -193,6 +193,44 @@ public ResponseEntity getCropsByStatus(@Parameter(description = "Estado de lo } } + /** + * Recupera todos los estados de cultivo registrados en el sistema. + *

Este método obtiene una lista de todos los estados de cultivo disponibles en el sistema. Si no se encuentran estados de cultivo, + * se lanzará una excepción y se devolverá un código HTTP 404 con un mensaje de error.

+ * + * @return Un objeto {@link ResponseEntity} que contiene: + *
    + *
  • Una lista de cadenas {@link String} con los estados de cultivo (código HTTP 200).
  • + *
  • Un mensaje de error si ocurre un problema al obtener los estados de cultivo o no se encuentran registrados (código HTTP 404).
  • + *
+ * + *

Respuestas posibles:

+ *
    + *
  • 200 OK: Si se encuentran estados de cultivo registrados, se retorna una lista de cadenas con los nombres de los estados de cultivo en formato JSON.
  • + *
  • 404 Not Found: Si no se encuentran estados de cultivo registrados o ocurre un error al obtenerlos, se retorna un objeto {@link ErrorResponse} con un mensaje de error.
  • + *
+ */ + @GetMapping("/status/All") + @Operation(summary = "Obtener todos los estados de cultivo", + description = "Recupera todos los estados de cultivos registrados en el sistema. " + + "En caso de no haber estados de cultivos, se devolverá una excepción.", + responses = { + @ApiResponse(description = "Estados de cultivo encontrados", + responseCode = "200", + content = @Content(mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = String.class)))), + @ApiResponse(responseCode = "404", + description = "No se encontraron estados de cultivo registrados.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity getAllStatus() { + try { + return new ResponseEntity<>(serviceCrop.getAllStatus(), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al buscar los estados de cultivo [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); + } + } + /** * Busca cultivos por su tipo. *

Este método recupera una lista de cultivos en el sistema filtrados por su tipo. El parámetro `type` es utilizado para determinar el tipo de los cultivos que se desean recuperar. Si se encuentran cultivos con el tipo especificado, se devolverá una lista de objetos {@link CropDTO} que representan los cultivos encontrados.

@@ -231,6 +269,44 @@ public ResponseEntity getCropsByType(@Parameter(description = "Tipo de los cu } } + /** + * Recupera todos los tipos de cultivo registrados en el sistema. + *

Este método obtiene una lista de todos los tipos de cultivo disponibles en el sistema. Si no se encuentran tipos de cultivo, + * se lanzará una excepción y se devolverá un código HTTP 404 con un mensaje de error.

+ * + * @return Un objeto {@link ResponseEntity} que contiene: + *
    + *
  • Una lista de cadenas {@link String} con los tipos de cultivo (código HTTP 200).
  • + *
  • Un mensaje de error si ocurre un problema al obtener los tipos de cultivo o no se encuentran registrados (código HTTP 404).
  • + *
+ * + *

Respuestas posibles:

+ *
    + *
  • 200 OK: Si se encuentran tipos de cultivo registrados, se retorna una lista de cadenas con los nombres de los tipos de cultivo en formato JSON.
  • + *
  • 404 Not Found: Si no se encuentran tipos de cultivo registrados o ocurre un error al obtenerlos, se retorna un objeto {@link ErrorResponse} con un mensaje de error.
  • + *
+ */ + @GetMapping("/type/All") + @Operation(summary = "Obtener todos los tipos de cultivo", + description = "Recupera todos los tipos de cultivos registrados en el sistema. " + + "En caso de no haber tipos de cultivos, se devolverá una excepción.", + responses = { + @ApiResponse(description = "Tipos de cultivo encontrados", + responseCode = "200", + content = @Content(mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = String.class)))), + @ApiResponse(responseCode = "404", + description = "No se encontraron tipos de cultivo registrados.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity getAllTypes() { + try { + return new ResponseEntity<>(serviceCrop.getAllTypes(), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al buscar los tipos de cultivo [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); + } + } + /** * Busca todos los cultivos asociados a un usuario específico. *

Este método recupera todos los cultivos pertenecientes a un usuario específico utilizando su ID único. Si el usuario con el ID proporcionado tiene cultivos, se devolverá una lista de objetos {@link CropDTO} con la información de cada cultivo.

diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index a41d614..fecf734 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -10,9 +10,10 @@ import smartpot.com.api.Crops.Mapper.MCrop; import smartpot.com.api.Crops.Model.DAO.Repository.RCrop; import smartpot.com.api.Crops.Model.DTO.CropDTO; +import smartpot.com.api.Crops.Model.Entity.Status; +import smartpot.com.api.Crops.Model.Entity.Type; import smartpot.com.api.Crops.Validation.VCropI; import smartpot.com.api.Users.Model.DAO.Service.SUserI; - import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -83,7 +84,12 @@ public CropDTO createCrop(CropDTO cropDTO) throws Exception { return Optional.of(cropDTO) .map(ValidCropDTO -> { validatorCrop.validateType(ValidCropDTO.getType()); - validatorCrop.validateUser(ValidCropDTO.getUser()); + try { + serviceUser.getUserById(ValidCropDTO.getUser()); + } catch (Exception e) { + throw new ValidationException("Error"+e); + } + if (validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); } @@ -191,6 +197,23 @@ public List getCropsByUser(String id) throws Exception { .orElseThrow(() -> new Exception("No tiene ningún cultivo")); } + /** + * Cuenta la cantidad de cultivos asociados a un usuario específico. + * + *

Este método obtiene la lista de cultivos asociados a un usuario mediante su ID utilizando el método + * {@link #getCropsByUser(String)}, y luego cuenta la cantidad de cultivos encontrados. Si el usuario no tiene cultivos, + * el método devolverá un valor de 0.

+ * + * @param id El identificador del usuario cuyos cultivos se desean contar. El ID debe ser una cadena que representa un {@link ObjectId}. + * @return El número de cultivos asociados al usuario especificado. + * @throws Exception Sí ocurre algún error al obtener los cultivos del usuario. + * @see #getCropsByUser(String) + */ + @Override + public long countCropsByUser(String id) throws Exception { + return getCropsByUser(id).size(); + } + /** * Obtiene una lista de cultivos de la base de datos según el tipo proporcionado. * @@ -230,23 +253,25 @@ public List getCropsByType(String type) throws Exception { } /** - * Cuenta la cantidad de cultivos asociados a un usuario específico. + * Obtiene una lista de todos los tipos de cultivo registrados en el sistema. * - *

Este método obtiene la lista de cultivos asociados a un usuario mediante su ID utilizando el método - * {@link #getCropsByUser(String)}, y luego cuenta la cantidad de cultivos encontrados. Si el usuario no tiene cultivos, - * el método devolverá un valor de 0.

+ *

Este método consulta todos los tipos de cultivo disponibles. Si no se encuentran tipos de cultivo, + * se lanza una excepción que indica que no existen tipos registrados.

* - * @param id El identificador del usuario cuyos cultivos se desean contar. El ID debe ser una cadena que representa un {@link ObjectId}. - * @return El número de cultivos asociados al usuario especificado. - * @throws Exception Sí ocurre algún error al obtener los cultivos del usuario. + * @return Una lista de cadenas {@link String} que representan los nombres de los tipos de cultivo encontrados. + * @throws Exception Si ocurre un error al obtener los tipos de cultivo o si no se encuentran tipos registrados. * - * @see #getCropsByUser(String) + * @see String + * @see Type */ @Override - public long countCropsByUser(String id) throws Exception { - return getCropsByUser(id).size(); + public List getAllTypes() throws Exception { + return Optional.of(Type.getTypeNames()) + .filter(types -> !types.isEmpty()) + .orElseThrow(() -> new Exception("No existe ningún tipo de cultivo")); } + /** * Obtiene una lista de cultivos de la base de datos según el estado proporcionado. * @@ -285,6 +310,25 @@ public List getCropsByStatus(String status) throws Exception { .orElseThrow(() -> new Exception("No existen cultivos")); } + /** + * Obtiene una lista de todos los estados de cultivo registrados en la base de datos. + * + *

Este método consulta los estados de cultivo disponibles en la base de datos. Si no se encuentran estados de cultivo, + * se lanza una excepción que indica que no existen estados registrados.

+ * + * @return Una lista de cadenas {@link String} que representan los estados de cultivo encontrados. + * @throws Exception Si ocurre un error al buscar los estados de cultivo o si no se encuentran estados registrados. + * + * @see String + * @see Status + */ + @Override + public List getAllStatus() throws Exception { + return Optional.of(Status.getStatusNames()) + .filter(status -> !status.isEmpty()) + .orElseThrow(() -> new Exception("No existe ningún estados para los cultivos")); + } + /** * Actualiza un cultivo existente en la base de datos con los nuevos datos proporcionados en un objeto {@link CropDTO}. * @@ -322,7 +366,11 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { .map(dto -> { validatorCrop.validateStatus(dto.getStatus()); validatorCrop.validateType(dto.getType()); - validatorCrop.validateUser(dto.getUser()); + try { + serviceUser.getUserById(existingCrop.getId()); + } catch (Exception e) { + throw new ValidationException("Error"+e); + } if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); } diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java index 53e04f0..867b06c 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java @@ -12,15 +12,20 @@ public interface SCropI { List getCropsByUser(String id) throws Exception; + long countCropsByUser(String id) throws Exception; + List getCropsByType(String type) throws Exception; - long countCropsByUser(String id) throws Exception; + List getAllTypes() throws Exception; List getCropsByStatus(String status) throws Exception; + List getAllStatus() throws Exception; + CropDTO createCrop(CropDTO newCropDto) throws Exception; CropDTO updatedCrop(String id, CropDTO cropDto) throws Exception; String deleteCrop(String id) throws Exception; + } diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java index 5b155f7..015a1a8 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java @@ -1,6 +1,21 @@ package smartpot.com.api.Crops.Model.Entity; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + public enum Status { Dead, Extreme_decomposition, Severe_deterioration, Moderate_deterioration, Healthy_state, intermittent, - Moderate_health, Good_health, Very_healthy, Excellent, Perfect_plant, Unknown + Moderate_health, Good_health, Very_healthy, Excellent, Perfect_plant, Unknown; + + /** + * Obtiene la lista de nombres de estados de cultivos definidos en el sistema. + * + * @return Una lista con los nombres de los estados. + */ + public static List getStatusNames() { + return Arrays.stream(Status.values()) + .map(Enum::name) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java index e43c0e9..510849a 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java @@ -1,5 +1,15 @@ package smartpot.com.api.Crops.Model.Entity; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + public enum Type { - TOMATO, LETTUCE + TOMATO, LETTUCE; + + public static List getTypeNames() { + return Arrays.stream(Type.values()) + .map(Enum::name) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java b/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java index f2f06b7..06436a9 100644 --- a/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java @@ -1,9 +1,14 @@ package smartpot.com.api.Crops.Validation; +import org.bson.types.ObjectId; import org.springframework.stereotype.Component; +import smartpot.com.api.Crops.Model.Entity.Status; +import smartpot.com.api.Crops.Model.Entity.Type; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; @Component public class VCrop implements VCropI { @@ -38,7 +43,13 @@ public boolean isValid() { @Override public void validateId(String id) { - + if (id == null || id.isEmpty()) { + errors.add("El Id no puede estar vacío"); + valid = false; + } else if (!ObjectId.isValid(id)) { + errors.add("El Id debe ser un hexadecimal de 24 caracteres"); + valid = false; + } } /** @@ -48,7 +59,9 @@ public void validateId(String id) { */ @Override public List getErrors() { - return errors; + List currentErrors = errors; + Reset(); + return currentErrors; } /** @@ -62,16 +75,31 @@ public void Reset() { @Override public void validateType(String type) { + if (type == null || type.isEmpty()) { + errors.add("El tipo de cultivo no puede estar vacío"); + valid = false; + } - } - - @Override - public void validateStatus(String validStatus) { + Set validTypes = new HashSet<>(Type.getTypeNames()); + if (!validTypes.contains(type)) { + errors.add("El Tipo de cultivo debe ser uno de los siguientes: " + String.join(", ", validTypes)); + valid = false; + } } @Override - public void validateUser(String user) { - + public void validateStatus(String status) { + if (status == null || status.isEmpty()) { + errors.add("El Estado del cultivo no puede estar vacío"); + valid = false; + } + Set validStatus = new HashSet<>(Status.getStatusNames()); + + if (!validStatus.contains(status)) { + errors.add("El Estado del cultivo debe ser uno de los siguientes: " + String.join(", ", validStatus)); + valid = false; + } } + } diff --git a/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java b/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java index ee712ed..35c5508 100644 --- a/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java +++ b/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java @@ -14,6 +14,4 @@ public interface VCropI { void validateType(String type); void validateStatus(String validStatus); - - void validateUser(String user); } diff --git a/src/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java index e873281..2e025b5 100644 --- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java +++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java @@ -108,7 +108,7 @@ public ResponseEntity createUser( @GetMapping("/All") @Operation(summary = "Obtener todos los usuarios", description = "Recupera todos los usuarios registrados en el sistema. " - + "En caso de no haber usuarios, se devolverá una lista vacía.", + + "En caso de no haber usuarios, se devolverá una excepción.", responses = { @ApiResponse(description = "Usuarios encontrados", responseCode = "200", @@ -126,6 +126,7 @@ public ResponseEntity getAllUsers() { } } + /** * Busca un usuario utilizando su ID único. *

Este método recupera un usuario de la base de datos utilizando su identificador único. Si el usuario con el ID proporcionado existe, se devolverá un objeto {@link UserDTO} con la información del usuario.

@@ -322,6 +323,44 @@ public ResponseEntity getUsersByRole(@Parameter(description = "Rol del usuari } } + /** + * Recupera todos los roles de usuario registrados en el sistema. + *

Este método obtiene una lista de todos los roles de usuario disponibles en el sistema. Si no se encuentran roles, + * se devolverá una lista vacía con el código HTTP 200.

+ * + * @return Un objeto {@link ResponseEntity} que contiene: + *
    + *
  • Una lista de cadenas {@link String} con los roles de usuario (código HTTP 200).
  • + *
  • Un mensaje de error si ocurre un problema al obtener los roles o no se encuentran roles registrados (código HTTP 404).
  • + *
+ * + *

Respuestas posibles:

+ *
    + *
  • 200 OK: Si se encuentran roles registrados, se retorna una lista de cadenas con los nombres de los roles en formato JSON.
  • + *
  • 404 Not Found: Si no se encuentran roles o ocurre un error al obtenerlos, se retorna un objeto {@link ErrorResponse} con un mensaje de error.
  • + *
+ */ + @GetMapping("/role/All") + @Operation(summary = "Obtener todos los roles de usuario", + description = "Recupera todos los roles de usuario registrados en el sistema. " + + "En caso de no haber roles de usuario, se devolverá una excepción.", + responses = { + @ApiResponse(description = "Roles encontrados", + responseCode = "200", + content = @Content(mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = String.class)))), + @ApiResponse(responseCode = "404", + description = "No se encontraron roles registrados.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity getAllRoles() { + try { + return new ResponseEntity<>(serviceUser.getAllRoles(), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al buscar los roles [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); + } + } + /** * Actualiza la información de un usuario existente. *

Este método recibe un ID de usuario y un objeto {@link UserDTO} con los datos actualizados. diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index 639f1b4..57b470f 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -12,6 +12,7 @@ import smartpot.com.api.Users.Mapper.MUser; import smartpot.com.api.Users.Model.DAO.Repository.RUser; import smartpot.com.api.Users.Model.DTO.UserDTO; +import smartpot.com.api.Users.Model.Entity.Role; import smartpot.com.api.Users.Validation.VUserI; import java.text.SimpleDateFormat; @@ -291,6 +292,23 @@ public List getUsersByRole(String role) throws Exception { .orElseThrow(() -> new Exception("No existe ningún usuario con el rol")); } + /** + * Obtiene todos los roles de usuario de la base de datos. + * * + * Este método consulta todos los roles de usuario almacenados en la base de datos utilizando el + * enum `Role`. Si la lista de roles de usuario está vacía, lanza una excepción. + * + * @return una lista de objetos {@link String} que representan a todos los roles de usuario. + * @throws Exception si no se encuentra ningún rol de usuario en la base de datos. + * @see Role + */ + @Override + public List getAllRoles() throws Exception { + return Optional.of(Role.getRoleNames()) + .filter(roles -> !roles.isEmpty()) + .orElseThrow(() -> new Exception("No existe ningún rol")); + } + /** * Actualiza la información de un usuario en la base de datos. * * diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java index d6b53f2..9299b9c 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java @@ -76,6 +76,14 @@ public interface SUserI extends UserDetailsService { */ List getUsersByRole(String role) throws Exception; + /** + * Obtiene todos los roles de usuario registrados en el sistema. + * + * @return una lista de objetos {@link String} que representan a todos los roles de usuario. + * @throws Exception si ocurre un error al obtener los roles de usuario. + */ + List getAllRoles() throws Exception; + /** * Actualiza los datos de un usuario. * @@ -94,4 +102,5 @@ public interface SUserI extends UserDetailsService { * @throws Exception si no se encuentra el usuario o si ocurre un error durante la eliminación. */ String DeleteUser(String id) throws Exception; + } diff --git a/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java b/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java index 2334658..bf10ae2 100644 --- a/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java +++ b/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java @@ -1,5 +1,20 @@ package smartpot.com.api.Users.Model.Entity; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + public enum Role { - USER, ADMIN, SYSTEM + USER, ADMIN, SYSTEM; + + /** + * Obtiene la lista de nombres de roles definidos en el sistema. + * + * @return Una lista con los nombres de los roles. + */ + public static List getRoleNames() { + return Arrays.stream(Role.values()) + .map(Enum::name) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/smartpot/com/api/Users/Validation/VUser.java b/src/main/java/smartpot/com/api/Users/Validation/VUser.java index 9b1f77d..76f41a0 100644 --- a/src/main/java/smartpot/com/api/Users/Validation/VUser.java +++ b/src/main/java/smartpot/com/api/Users/Validation/VUser.java @@ -5,7 +5,9 @@ import smartpot.com.api.Users.Model.Entity.Role; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import static smartpot.com.api.Users.Validation.UserRegex.*; @@ -55,7 +57,9 @@ public boolean isValid() { */ @Override public List getErrors() { - return errors; + List currentErrors = errors; + Reset(); + return currentErrors; } /** @@ -163,32 +167,14 @@ public void validateRole(String role) { if (role == null || role.isEmpty()) { errors.add("El rol no puede estar vacío"); valid = false; - } else { - boolean isValidRole = false; - for (String validRole : getRoleNames()) { - if (validRole.matches(role)) { - isValidRole = true; - break; - } - } - - if (!isValidRole) { - errors.add("El Rol debe ser uno de los siguientes: " + String.join(", ", getRoleNames())); - valid = false; - } + return; } - } - /** - * Obtiene la lista de nombres de roles definidos en el sistema. - * - * @return Una lista con los nombres de los roles. - */ - private List getRoleNames() { - List roleNames = new ArrayList<>(); - for (Role role : Role.values()) { - roleNames.add(role.name()); + Set validRoles = new HashSet<>(Role.getRoleNames()); + + if (!validRoles.contains(role)) { + errors.add("El Rol debe ser uno de los siguientes: " + String.join(", ", validRoles)); + valid = false; } - return roleNames; } } diff --git a/src/test/java/smartpot/com/api/SmartPotApiApplicationTest.java b/src/test/java/smartpot/com/api/SmartPotApiApplicationTest.java new file mode 100644 index 0000000..3723940 --- /dev/null +++ b/src/test/java/smartpot/com/api/SmartPotApiApplicationTest.java @@ -0,0 +1,12 @@ +package smartpot.com.api; + +import org.junit.jupiter.api.Test; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(locations = "classpath:/application.properties") +class SmartPotApiApplicationTest { + + @Test + void contextLoads() { + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..8f3e38c --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,44 @@ +# Configuración APP +spring.application.name=SmartPot-API-Test +server.port=8091 +application.title=SmartPot-API-Test +application.description=Entorno de Prueba para API de SmartPot +application.version=1.0.0 +application.author=SmartPot Developers +# Conexión de MongoDB +spring.data.mongodb.uri=mongo://localhost:27017/smartpot +# Security Config +application.security.jwt.secret-key=mySuperSecretKey +application.security.jwt.expiration=4102444800 +application.security.public.routes=/** +# Swagger Config +springdoc.api-docs.enabled=true +springdoc.swagger-ui.enabled=true +springdoc.swagger-ui.path=/ +springdoc.swagger-ui.url=/v3/api-docs +springdoc.swagger-ui.display-operation-id=false +springdoc.swagger-ui.display-request-duration=true +springdoc.swagger-ui.default-model-rendering=example +springdoc.swagger-ui.default-model-expand-depth=1 +springdoc.swagger-ui.doc-expansion=list +# Http Headers +http.header.cors.allowedOrigins=* +# Config TOMCAT +server.tomcat.connection-timeout=100000000 +# Logs +# Activar logs de seguridad para JWT +logging.level.org.springframework.security=DEBUG +logging.level.smartpot.com.api.Security.jwt=DEBUG +# Habilitar logs de headers HTTP y filtros +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.web.filter=DEBUG +# Logs de MongoDB +logging.level.org.springframework.data.mongodb=DEBUG +logging.level.com.mongodb=DEBUG +# Logs de Swagger +logging.level.org.springdoc=DEBUG +logging.level.io.swagger=DEBUG +# Logs generales de Spring Boot +logging.level.org.springframework=DEBUG +logging.level.root=DEBUG + From 4bf1f2035b6358681e87a6e9ae63a6b0fed88265 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Wed, 1 Jan 2025 22:22:46 -0500 Subject: [PATCH 06/25] fix for using isValid in crop validation --- .../com/api/Crops/Model/DAO/Service/SCrop.java | 14 +++++++------- .../smartpot/com/api/Crops/Model/DTO/CropDTO.java | 2 +- .../api/Crops/{Validation => Validator}/VCrop.java | 2 +- .../Crops/{Validation => Validator}/VCropI.java | 2 +- .../com/api/Users/Model/DAO/Service/SUser.java | 2 +- .../Users/{Validation => Validator}/UserRegex.java | 2 +- .../api/Users/{Validation => Validator}/VUser.java | 4 ++-- .../Users/{Validation => Validator}/VUserI.java | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) rename src/main/java/smartpot/com/api/Crops/{Validation => Validator}/VCrop.java (98%) rename src/main/java/smartpot/com/api/Crops/{Validation => Validator}/VCropI.java (84%) rename src/main/java/smartpot/com/api/Users/{Validation => Validator}/UserRegex.java (98%) rename src/main/java/smartpot/com/api/Users/{Validation => Validator}/VUser.java (98%) rename src/main/java/smartpot/com/api/Users/{Validation => Validator}/VUserI.java (98%) diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index fecf734..dfb508a 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -12,7 +12,7 @@ import smartpot.com.api.Crops.Model.DTO.CropDTO; import smartpot.com.api.Crops.Model.Entity.Status; import smartpot.com.api.Crops.Model.Entity.Type; -import smartpot.com.api.Crops.Validation.VCropI; +import smartpot.com.api.Crops.Validator.VCropI; import smartpot.com.api.Users.Model.DAO.Service.SUserI; import java.util.List; import java.util.Optional; @@ -87,10 +87,10 @@ public CropDTO createCrop(CropDTO cropDTO) throws Exception { try { serviceUser.getUserById(ValidCropDTO.getUser()); } catch (Exception e) { - throw new ValidationException("Error"+e); + throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente."); } - if (validatorCrop.isValid()) { + if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); } validatorCrop.Reset(); @@ -154,7 +154,7 @@ public CropDTO getCropById(String id) throws Exception { return Optional.of(id) .map(ValidCropId -> { validatorCrop.validateId(ValidCropId); - if (validatorCrop.isValid()) { + if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); } validatorCrop.Reset(); @@ -238,7 +238,7 @@ public List getCropsByType(String type) throws Exception { return Optional.of(type) .map(ValidType -> { validatorCrop.validateType(ValidType); - if (validatorCrop.isValid()) { + if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); } validatorCrop.Reset(); @@ -296,7 +296,7 @@ public List getCropsByStatus(String status) throws Exception { return Optional.of(status) .map(ValidStatus -> { validatorCrop.validateStatus(ValidStatus); - if (validatorCrop.isValid()) { + if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); } validatorCrop.Reset(); @@ -369,7 +369,7 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { try { serviceUser.getUserById(existingCrop.getId()); } catch (Exception e) { - throw new ValidationException("Error"+e); + throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente."); } if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); diff --git a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java index ea02049..456d558 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java @@ -20,7 +20,7 @@ public class CropDTO { private String id; @Schema(description = "Estado actual del cultivo.", - example = "Perfect_plant", hidden = true) + example = "Perfect_plant") private String status; @Schema(description = "Tipo de cultivo.", diff --git a/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java b/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java similarity index 98% rename from src/main/java/smartpot/com/api/Crops/Validation/VCrop.java rename to src/main/java/smartpot/com/api/Crops/Validator/VCrop.java index 06436a9..e399ee2 100644 --- a/src/main/java/smartpot/com/api/Crops/Validation/VCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Crops.Validation; +package smartpot.com.api.Crops.Validator; import org.bson.types.ObjectId; import org.springframework.stereotype.Component; diff --git a/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java b/src/main/java/smartpot/com/api/Crops/Validator/VCropI.java similarity index 84% rename from src/main/java/smartpot/com/api/Crops/Validation/VCropI.java rename to src/main/java/smartpot/com/api/Crops/Validator/VCropI.java index 35c5508..697eee0 100644 --- a/src/main/java/smartpot/com/api/Crops/Validation/VCropI.java +++ b/src/main/java/smartpot/com/api/Crops/Validator/VCropI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Crops.Validation; +package smartpot.com.api.Crops.Validator; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index 57b470f..3c2bba0 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -13,7 +13,7 @@ import smartpot.com.api.Users.Model.DAO.Repository.RUser; import smartpot.com.api.Users.Model.DTO.UserDTO; import smartpot.com.api.Users.Model.Entity.Role; -import smartpot.com.api.Users.Validation.VUserI; +import smartpot.com.api.Users.Validator.VUserI; import java.text.SimpleDateFormat; import java.util.Date; diff --git a/src/main/java/smartpot/com/api/Users/Validation/UserRegex.java b/src/main/java/smartpot/com/api/Users/Validator/UserRegex.java similarity index 98% rename from src/main/java/smartpot/com/api/Users/Validation/UserRegex.java rename to src/main/java/smartpot/com/api/Users/Validator/UserRegex.java index 4030d45..60a5624 100644 --- a/src/main/java/smartpot/com/api/Users/Validation/UserRegex.java +++ b/src/main/java/smartpot/com/api/Users/Validator/UserRegex.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Users.Validation; +package smartpot.com.api.Users.Validator; /** * Contiene las expresiones regulares para la validación de los campos de usuario. diff --git a/src/main/java/smartpot/com/api/Users/Validation/VUser.java b/src/main/java/smartpot/com/api/Users/Validator/VUser.java similarity index 98% rename from src/main/java/smartpot/com/api/Users/Validation/VUser.java rename to src/main/java/smartpot/com/api/Users/Validator/VUser.java index 76f41a0..6b27ab7 100644 --- a/src/main/java/smartpot/com/api/Users/Validation/VUser.java +++ b/src/main/java/smartpot/com/api/Users/Validator/VUser.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Users.Validation; +package smartpot.com.api.Users.Validator; import org.bson.types.ObjectId; import org.springframework.stereotype.Component; @@ -10,7 +10,7 @@ import java.util.Set; import java.util.regex.Pattern; -import static smartpot.com.api.Users.Validation.UserRegex.*; +import static smartpot.com.api.Users.Validator.UserRegex.*; /** * Clase de validación para los usuarios. diff --git a/src/main/java/smartpot/com/api/Users/Validation/VUserI.java b/src/main/java/smartpot/com/api/Users/Validator/VUserI.java similarity index 98% rename from src/main/java/smartpot/com/api/Users/Validation/VUserI.java rename to src/main/java/smartpot/com/api/Users/Validator/VUserI.java index e276c34..0bc285a 100644 --- a/src/main/java/smartpot/com/api/Users/Validation/VUserI.java +++ b/src/main/java/smartpot/com/api/Users/Validator/VUserI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Users.Validation; +package smartpot.com.api.Users.Validator; import java.util.List; From 9270511e63531d31e6ddfc50ad678f2bdf9d611c Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Wed, 1 Jan 2025 22:25:19 -0500 Subject: [PATCH 07/25] Update SCrop.java --- .../java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index dfb508a..2d13a72 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -87,7 +87,7 @@ public CropDTO createCrop(CropDTO cropDTO) throws Exception { try { serviceUser.getUserById(ValidCropDTO.getUser()); } catch (Exception e) { - throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente."); + throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente"); } if (!validatorCrop.isValid()) { @@ -369,7 +369,7 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { try { serviceUser.getUserById(existingCrop.getId()); } catch (Exception e) { - throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente."); + throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente"); } if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); From dc2b47c310b34db0183f6b6337a1f72ba55e3b54 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Wed, 1 Jan 2025 23:56:37 -0500 Subject: [PATCH 08/25] Implementation of mail service using Google's SMTP protocol --- .env.example | 8 +++ .../Mail/Model/DAO/Service/EmailService.java | 63 +++++++++++++++++++ .../Mail/Model/DAO/Service/EmailServiceI.java | 10 +++ .../api/Mail/Model/Entity/EmailDetails.java | 15 +++++ .../Security/Controller/AuthController.java | 6 +- .../com/api/Security/Service/JwtService.java | 18 ++++-- src/main/resources/application.properties | 8 +++ 7 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java create mode 100644 src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java create mode 100644 src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java diff --git a/.env.example b/.env.example index 4722547..c17126a 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,14 @@ DATA_SOURCE_DOMAIN=domain DATA_SOURCE_DB=database DATA_PARAMS=params +# Email Credentials +MAIL_HOST=SMTP.GMAIL.COM +MAIL_PORT=587 +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_PROPERTIES_SMTP_AUTH=TRUE +MAIL_PROPERTIES_SMTP_STARTTLS_ENABLE=TRUE + # JWT Credentials SECURITY_JWT_SECRET_KEY=secret-key SECURITY_JWT_EXPIRATION=jwt-expiration diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java new file mode 100644 index 0000000..2a9a8af --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java @@ -0,0 +1,63 @@ +package smartpot.com.api.Mail.Model.DAO.Service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import smartpot.com.api.Mail.Model.Entity.EmailDetails; + +import java.io.File; +import java.util.Objects; + +@Service +public class EmailService implements EmailServiceI { + private final JavaMailSender javaMailSender; + + @Value("${spring.mail.username}") + private String sender; + + @Autowired + public EmailService(JavaMailSender javaMailSender) { + this.javaMailSender = javaMailSender; + } + + @Override + public String sendSimpleMail(EmailDetails details) { + try { + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setFrom(sender); + mailMessage.setTo(details.getRecipient()); + mailMessage.setText(details.getMsgBody()); + mailMessage.setSubject(details.getSubject()); + + javaMailSender.send(mailMessage); + return "Correo enviado exitosamente."; + } catch (Exception e) { + return "Error al enviar correo."; + } + } + + @Override + public String sendMailWithAttachment(EmailDetails details) { + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + MimeMessageHelper mimeMessageHelper; + try { + mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); + mimeMessageHelper.setFrom(sender); + mimeMessageHelper.setTo(details.getRecipient()); + mimeMessageHelper.setText(details.getMsgBody()); + mimeMessageHelper.setSubject(details.getSubject()); + FileSystemResource file = new FileSystemResource(new File(details.getAttachment())); + mimeMessageHelper.addAttachment(Objects.requireNonNull(file.getFilename()), file); + javaMailSender.send(mimeMessage); + return "Correo enviado exitosamente."; + } catch (MessagingException e) { + return "Error al enviar correo."; + } + } +} diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java new file mode 100644 index 0000000..ba32891 --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java @@ -0,0 +1,10 @@ +package smartpot.com.api.Mail.Model.DAO.Service; + +import smartpot.com.api.Mail.Model.Entity.EmailDetails; + +public interface EmailServiceI { + + String sendSimpleMail(EmailDetails details); + + String sendMailWithAttachment(EmailDetails details); +} diff --git a/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java b/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java new file mode 100644 index 0000000..31a5832 --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java @@ -0,0 +1,15 @@ +package smartpot.com.api.Mail.Model.Entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EmailDetails { + private String recipient; + private String msgBody; + private String subject; + private String attachment; +} diff --git a/src/main/java/smartpot/com/api/Security/Controller/AuthController.java b/src/main/java/smartpot/com/api/Security/Controller/AuthController.java index f7d990c..c4f6552 100644 --- a/src/main/java/smartpot/com/api/Security/Controller/AuthController.java +++ b/src/main/java/smartpot/com/api/Security/Controller/AuthController.java @@ -8,13 +8,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; import smartpot.com.api.Exception.InvalidTokenException; import smartpot.com.api.Responses.ErrorResponse; import smartpot.com.api.Responses.TokenResponse; import smartpot.com.api.Security.Service.JwtServiceI; -import smartpot.com.api.Users.Model.DAO.Service.SUserI; import smartpot.com.api.Users.Model.DTO.UserDTO; @RestController @@ -22,12 +20,10 @@ @Tag(name = "Autentificación", description = "Operaciones relacionadas con autentificación de usuarios") public class AuthController { - private final SUserI serviceUser; private final JwtServiceI jwtService; @Autowired - public AuthController(final SUserI serviceUser, final JwtServiceI jwtService) { - this.serviceUser = serviceUser; + public AuthController(JwtServiceI jwtService) { this.jwtService = jwtService; } diff --git a/src/main/java/smartpot/com/api/Security/Service/JwtService.java b/src/main/java/smartpot/com/api/Security/Service/JwtService.java index bfc6e74..e6c06f5 100644 --- a/src/main/java/smartpot/com/api/Security/Service/JwtService.java +++ b/src/main/java/smartpot/com/api/Security/Service/JwtService.java @@ -6,13 +6,12 @@ import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import smartpot.com.api.Exception.InvalidTokenException; -import smartpot.com.api.Responses.ErrorResponse; +import smartpot.com.api.Mail.Model.DAO.Service.EmailServiceI; +import smartpot.com.api.Mail.Model.Entity.EmailDetails; import smartpot.com.api.Users.Model.DAO.Service.SUserI; import smartpot.com.api.Users.Model.DTO.UserDTO; import javax.crypto.SecretKey; @@ -31,6 +30,7 @@ public class JwtService implements JwtServiceI { private long expiration; private final SUserI serviceUser; + private final EmailServiceI emailService; /** * Constructor que inyecta las dependencias del servicio. @@ -38,8 +38,9 @@ public class JwtService implements JwtServiceI { * @param serviceUser servicio que maneja las operaciones de base de datos. */ @Autowired - public JwtService(SUserI serviceUser) { + public JwtService(SUserI serviceUser, EmailServiceI emailService) { this.serviceUser = serviceUser; + this.emailService = emailService; } @Override @@ -47,6 +48,15 @@ public String Login(UserDTO reqUser) throws Exception { return Optional.of(serviceUser.getUserByEmail(reqUser.getEmail())) .filter( userDTO -> new BCryptPasswordEncoder().matches(reqUser.getPassword(), userDTO.getPassword())) .map(validUser -> generateToken(validUser.getId(), validUser.getEmail())) + .map(validToken -> { + emailService.sendSimpleMail( + new EmailDetails("smartpottech@gmail.com", + "Se ha iniciado sesion en su cuenta, verifique su token de seguridad '"+validToken+"'", + "Inicio de Sesion en Smartpot", + "" + )); + return validToken; + }) .orElseThrow(() -> new Exception("Credenciales Invalidas")); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4cfd854..b1d96e0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -25,6 +25,14 @@ springdoc.swagger-ui.default-model-rendering=example springdoc.swagger-ui.default-model-expand-depth=1 springdoc.swagger-ui.doc-expansion=list +# Email Credentials +spring.mail.host=${MAIL_HOST} +spring.mail.port=${MAIL_PORT} +spring.mail.username=${MAIL_USERNAME} +spring.mail.password=${MAIL_PASSWORD} +spring.mail.properties.mail.smtp.auth=${MAIL_PROPERTIES_SMTP_AUTH} +spring.mail.properties.mail.smtp.starttls.enable=${MAIL_PROPERTIES_SMTP_STARTTLS_ENABLE} + # Http Headers http.header.cors.allowedOrigins=${HEADER_CORS_ALLOWED_ORIGINS} From 0255cb727bb27bea8df19c045fc9a308eef953ac Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Thu, 2 Jan 2025 12:51:34 -0500 Subject: [PATCH 09/25] Implementation of controller for mail dispatcher service --- .../com/api/Documentation/SwaggerConfig.java | 2 +- .../api/Mail/Controller/EmailController.java | 75 +++++++++++++++++++ .../com/api/Mail/Mapper/EmailMapper.java | 26 +++++++ .../Model/DAO/Repository/EmailRepository.java | 10 +++ .../Mail/Model/DAO/Service/EmailService.java | 61 ++++++++++++--- .../Mail/Model/DAO/Service/EmailServiceI.java | 9 ++- .../com/api/Mail/Model/DTO/EmailDTO.java | 16 ++++ .../api/Mail/Model/Entity/EmailDetails.java | 20 ++++- .../com/api/Security/Service/JwtService.java | 4 +- 9 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 src/main/java/smartpot/com/api/Mail/Controller/EmailController.java create mode 100644 src/main/java/smartpot/com/api/Mail/Mapper/EmailMapper.java create mode 100644 src/main/java/smartpot/com/api/Mail/Model/DAO/Repository/EmailRepository.java create mode 100644 src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java diff --git a/src/main/java/smartpot/com/api/Documentation/SwaggerConfig.java b/src/main/java/smartpot/com/api/Documentation/SwaggerConfig.java index 7a01baa..50f0093 100644 --- a/src/main/java/smartpot/com/api/Documentation/SwaggerConfig.java +++ b/src/main/java/smartpot/com/api/Documentation/SwaggerConfig.java @@ -33,7 +33,7 @@ public OpenAPI customOpenAPI() { .description(description) .termsOfService("https://github.com/SmarPotTech/SmartPot-API/blob/main/LICENSE") .license(new License().name("MIT License").url("https://opensource.org/license/mit")) - .contact(new Contact().name(author).url("https://github.com/SmarPotTech")) + .contact(new Contact().name(author).url("https://github.com/SmarPotTech").email("smartpottech@gmail.com")) ); } } \ No newline at end of file diff --git a/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java b/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java new file mode 100644 index 0000000..3618a9c --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java @@ -0,0 +1,75 @@ +package smartpot.com.api.Mail.Controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import smartpot.com.api.Mail.Model.DAO.Service.EmailServiceI; +import smartpot.com.api.Mail.Model.DTO.EmailDTO; +import smartpot.com.api.Responses.ErrorResponse; + +@RestController +@RequestMapping("/Emails") +@Tag(name = "Correos", description = "Operaciones relacionadas con correos") +public class EmailController { + private final EmailServiceI emailServiceI; + + /** + * Constructor del controlador {@link EmailController}. + *

Se utiliza la inyección de dependencias para asignar el servicio {@link EmailServiceI} que gestionará las operaciones + * relacionadas con los correos.

+ * + * @param emailServiceI El servicio que contiene la lógica de negocio para manejar correos. + * @throws NullPointerException Si el servicio proporcionado es {@code null}. + * @see EmailServiceI + */ + @Autowired + public EmailController(EmailServiceI emailServiceI) { + this.emailServiceI = emailServiceI; + } + + /** + * Recupera todos los correos registrados en el sistema. + *

Este método devuelve una lista con todos los correos que están registrados en el sistema.

+ *

Si no se encuentran coreos, se devolverá una lista vacía con el código HTTP 200.

+ *

En caso de error (por ejemplo, problemas con la conexión a la base de datos o un fallo en el servicio), + * se devolverá un mensaje de error con el código HTTP 404.

+ * + * @return Un objeto {@link ResponseEntity} que contiene una lista de todos los correos registrados (código HTTP 200). + * En caso de error, se devolverá un mensaje de error con el código HTTP 404. + * + *

Respuestas posibles:

+ *
    + *
  • 200 OK: Se retorna una lista de objetos {@link EmailDTO} con la información de todos los correos registrados.
  • + *
  • 404 Not Found: No se encontraron correos registrados o hubo un error al recuperar los datos.
  • + *
+ */ + @GetMapping("/All") + @Operation(summary = "Obtener todos los correos", + description = "Recupera todos los correos registrados en el sistema. " + + "En caso de no haber correos, se devolverá una excepción.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(description = "Correos encontrados", + responseCode = "200", + content = @Content(mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = EmailDTO.class)))), + @ApiResponse(responseCode = "404", + description = "No se encontraron correos registrados.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity getAllCrops() { + try { + return new ResponseEntity<>(emailServiceI.getAllEmails(), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); + } + } +} diff --git a/src/main/java/smartpot/com/api/Mail/Mapper/EmailMapper.java b/src/main/java/smartpot/com/api/Mail/Mapper/EmailMapper.java new file mode 100644 index 0000000..bdba046 --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Mapper/EmailMapper.java @@ -0,0 +1,26 @@ +package smartpot.com.api.Mail.Mapper; + +import org.bson.types.ObjectId; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import smartpot.com.api.Mail.Model.DTO.EmailDTO; +import smartpot.com.api.Mail.Model.Entity.EmailDetails; + +@Mapper(componentModel = "spring") +public interface EmailMapper { + @Mapping(source = "id", target = "id", qualifiedByName = "stringToObjectId") + EmailDetails toEntity(EmailDTO emailDTO); + + @Mapping(source = "id", target = "id", qualifiedByName = "objectIdToString") + EmailDTO toDTO(EmailDetails emailDetails); + + @org.mapstruct.Named("objectIdToString") + default String objectIdToString(ObjectId objectId) { + return objectId != null ? objectId.toHexString() : null; + } + + @org.mapstruct.Named("stringToObjectId") + default ObjectId stringToObjectId(String id) { + return id != null ? new ObjectId(id) : null; + } +} diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Repository/EmailRepository.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Repository/EmailRepository.java new file mode 100644 index 0000000..06005d6 --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Repository/EmailRepository.java @@ -0,0 +1,10 @@ +package smartpot.com.api.Mail.Model.DAO.Repository; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; +import smartpot.com.api.Mail.Model.Entity.EmailDetails; + +@Repository +public interface EmailRepository extends MongoRepository { + +} diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java index 2a9a8af..c6ea31f 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java @@ -2,48 +2,63 @@ import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import smartpot.com.api.Mail.Mapper.EmailMapper; +import smartpot.com.api.Mail.Model.DAO.Repository.EmailRepository; +import smartpot.com.api.Mail.Model.DTO.EmailDTO; import smartpot.com.api.Mail.Model.Entity.EmailDetails; import java.io.File; +import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +@Slf4j @Service public class EmailService implements EmailServiceI { - private final JavaMailSender javaMailSender; - @Value("${spring.mail.username}") private String sender; + private final JavaMailSender javaMailSender; + private final EmailRepository emailRepository; + private final EmailMapper emailMapper; + @Autowired - public EmailService(JavaMailSender javaMailSender) { + public EmailService(JavaMailSender javaMailSender, EmailRepository emailRepository, EmailMapper emailMapper) { this.javaMailSender = javaMailSender; + this.emailRepository = emailRepository; + this.emailMapper = emailMapper; } @Override - public String sendSimpleMail(EmailDetails details) { + @Async + public void sendSimpleMail(EmailDetails details) { try { SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setFrom(sender); mailMessage.setTo(details.getRecipient()); mailMessage.setText(details.getMsgBody()); mailMessage.setSubject(details.getSubject()); - javaMailSender.send(mailMessage); - return "Correo enviado exitosamente."; + emailRepository.save(details); + log.warn("Correo Enviado Exitosamente"); } catch (Exception e) { - return "Error al enviar correo."; + log.error("Error al Enviar Correo "+e.getMessage()); } } @Override - public String sendMailWithAttachment(EmailDetails details) { + @Async + public void sendMailWithAttachment(EmailDetails details) { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper; try { @@ -55,9 +70,33 @@ public String sendMailWithAttachment(EmailDetails details) { FileSystemResource file = new FileSystemResource(new File(details.getAttachment())); mimeMessageHelper.addAttachment(Objects.requireNonNull(file.getFilename()), file); javaMailSender.send(mimeMessage); - return "Correo enviado exitosamente."; - } catch (MessagingException e) { - return "Error al enviar correo."; + emailRepository.save(details); + log.warn("Correo Enviado Exitosamente"); + } catch (Exception e) { + log.error("Error al Enviar Correo "+e.getMessage()); } } + + /** + * Obtiene todos los correos registrados en la base de datos y los convierte en objetos DTO. + * + *

Este método consulta todos los correos almacenados en la base de datos mediante el repositorio {@link EmailRepository}. + * Si la lista de correos está vacía, se lanza una excepción. Los correos obtenidos se mapean a objetos + * {@link EmailDTO} utilizando el convertidor {@link EmailMapper}.

+ * + * @return Una lista de objetos {@link EmailDTO} que representan todos los correos en la base de datos. + * @throws Exception Si no existen correos registrados en la base de datos. + * @see EmailDTO + * @see EmailRepository + * @see EmailMapper + */ + @Override + public List getAllEmails() throws Exception { + return Optional.of(emailRepository.findAll()) + .filter(emails -> !emails.isEmpty()) + .map(emails -> emails.stream() + .map(emailMapper::toDTO) + .collect(Collectors.toList())) + .orElseThrow(() -> new Exception("No existe ningún correo")); + } } diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java index ba32891..6659c63 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java @@ -1,10 +1,15 @@ package smartpot.com.api.Mail.Model.DAO.Service; +import smartpot.com.api.Mail.Model.DTO.EmailDTO; import smartpot.com.api.Mail.Model.Entity.EmailDetails; +import java.util.List; + public interface EmailServiceI { - String sendSimpleMail(EmailDetails details); + void sendSimpleMail(EmailDetails details); + + void sendMailWithAttachment(EmailDetails details); - String sendMailWithAttachment(EmailDetails details); + List getAllEmails() throws Exception; } diff --git a/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java b/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java new file mode 100644 index 0000000..22b8104 --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java @@ -0,0 +1,16 @@ +package smartpot.com.api.Mail.Model.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@AllArgsConstructor +@RequiredArgsConstructor +public class EmailDTO { + private String id; + private String recipient; + private String msgBody; + private String subject; + private String attachment; +} diff --git a/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java b/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java index 31a5832..eb7fc2b 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java +++ b/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java @@ -3,13 +3,31 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor -public class EmailDetails { +@Document(collection = "correos") +public class EmailDetails implements Serializable { + @Id + @Field("_id") + private ObjectId id; + + @Field("recipient") private String recipient; + + @Field("msgBody") private String msgBody; + + @Field("subject") private String subject; + + @Field("attachment") private String attachment; } diff --git a/src/main/java/smartpot/com/api/Security/Service/JwtService.java b/src/main/java/smartpot/com/api/Security/Service/JwtService.java index e6c06f5..afcf845 100644 --- a/src/main/java/smartpot/com/api/Security/Service/JwtService.java +++ b/src/main/java/smartpot/com/api/Security/Service/JwtService.java @@ -50,7 +50,9 @@ public String Login(UserDTO reqUser) throws Exception { .map(validUser -> generateToken(validUser.getId(), validUser.getEmail())) .map(validToken -> { emailService.sendSimpleMail( - new EmailDetails("smartpottech@gmail.com", + new EmailDetails( + null, + "smartpottech@gmail.com", "Se ha iniciado sesion en su cuenta, verifique su token de seguridad '"+validToken+"'", "Inicio de Sesion en Smartpot", "" From bab6661dce5529b30b7de7997fdbcbb8be17f59b Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Thu, 2 Jan 2025 12:56:04 -0500 Subject: [PATCH 10/25] Update JwtService.java --- .../java/smartpot/com/api/Security/Service/JwtService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/smartpot/com/api/Security/Service/JwtService.java b/src/main/java/smartpot/com/api/Security/Service/JwtService.java index afcf845..ba6bf1a 100644 --- a/src/main/java/smartpot/com/api/Security/Service/JwtService.java +++ b/src/main/java/smartpot/com/api/Security/Service/JwtService.java @@ -14,6 +14,7 @@ import smartpot.com.api.Mail.Model.Entity.EmailDetails; import smartpot.com.api.Users.Model.DAO.Service.SUserI; import smartpot.com.api.Users.Model.DTO.UserDTO; + import javax.crypto.SecretKey; import java.util.Date; import java.util.HashMap; @@ -75,7 +76,7 @@ private String generateToken(String id, String email) { } @Override - public UserDTO validateAuthHeader(String authHeader) throws Exception, InvalidTokenException { + public UserDTO validateAuthHeader(String authHeader) throws Exception { if (authHeader == null || !authHeader.startsWith("Bearer ")) { throw new Exception("El encabezado de autorización es inválido. Se esperaba 'Bearer '."); } From b19759f839fe25a2998897ee73e273d1b9ba891b Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Thu, 2 Jan 2025 13:02:06 -0500 Subject: [PATCH 11/25] Setting up sending emails asynchronously --- .../com/api/Mail/Config/AsyncConfig.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java diff --git a/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java new file mode 100644 index 0000000..e64b8e5 --- /dev/null +++ b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java @@ -0,0 +1,70 @@ +package smartpot.com.api.Mail.Config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.core.task.TaskExecutor; + +/** + * Clase de configuración para habilitar y gestionar la ejecución asincrónica en la aplicación. + * + *

Esta clase habilita la ejecución de tareas asincrónicas mediante la anotación {@link Async} en la aplicación Spring. + * Además, configura un {@link ThreadPoolTaskExecutor} para gestionar la ejecución de tareas en un pool de hilos de manera + * eficiente. El pool de hilos permite controlar el número de hilos simultáneos, la capacidad de la cola de tareas y otras + * propiedades relacionadas con la gestión de hilos.

+ * + *

Usar esta configuración es recomendable para aplicaciones de producción, donde es importante tener control sobre la + * ejecución de tareas asincrónicas para optimizar el uso de recursos y mejorar el rendimiento.

+ * + *

Esta clase se activa con la anotación {@link EnableAsync}, lo que habilita el soporte de ejecución asincrónica en la + * aplicación Spring.

+ * + * @see org.springframework.scheduling.annotation.Async + * @see ThreadPoolTaskExecutor + * @see TaskExecutor + */ +@Configuration +@EnableAsync +public class AsyncConfig { + + /** + * Crea un {@link ThreadPoolTaskExecutor} que gestiona las tareas asincrónicas en un pool de hilos. + * + *

Este método define un pool de hilos con un número mínimo y máximo de hilos, y configura la capacidad de la cola + * de tareas pendientes. El ejecutor se utiliza para ejecutar tareas anotadas con {@link Async} de manera eficiente.

+ * + *

El {@link ThreadPoolTaskExecutor} asegura que las tareas asincrónicas no consuman recursos innecesarios al crear + * nuevos hilos de manera indiscriminada, y permite la reutilización de hilos para mejorar el rendimiento en aplicaciones + * de alto rendimiento.

+ * + *

Los parámetros configurados en el {@link ThreadPoolTaskExecutor} son:

+ * + *
    + *
  • corePoolSize: El número mínimo de hilos que se deben mantener en el pool de hilos, incluso si están + * inactivos. Este valor asegura que siempre haya una cantidad mínima de hilos disponibles para ejecutar tareas + * sin la necesidad de crear hilos nuevos.
  • + *
  • maxPoolSize: El número máximo de hilos que pueden ejecutarse simultáneamente en el pool. Cuando el número + * de tareas concurrentes es mayor que el número de hilos en el pool, se creará un nuevo hilo hasta que se alcance + * este límite.
  • + *
  • queueCapacity: La capacidad de la cola de tareas pendientes. Cuando todos los hilos del pool están ocupados, + * las tareas adicionales se colocan en la cola. Si la cola se llena, y no se pueden crear más hilos, las tareas + * adicionales se rechazarán o esperarán hasta que haya espacio disponible.
  • + *
  • threadNamePrefix: El prefijo utilizado para nombrar los hilos creados por el ejecutor. Esto ayuda a identificar + * fácilmente los hilos de ejecución asincrónica en los registros (logs) y durante la depuración.
  • + *
+ * + * @return El {@link TaskExecutor} configurado, utilizado por Spring para ejecutar tareas asincrónicas. + */ + @Bean + public TaskExecutor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(25); + executor.setThreadNamePrefix("Async-Executor-"); + executor.initialize(); + return executor; + } +} From bf622a5e5f12e2130ad20e286e1b19e0db7875b0 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Thu, 2 Jan 2025 23:28:38 -0500 Subject: [PATCH 12/25] Configuring Redis as a cache point --- .env.example | 10 ++++ pom.xml | 10 ++++ .../smartpot/com/api/Cache/RedisConfig.java | 58 +++++++++++++++++++ .../com/api/SmartPotApiApplication.java | 2 + .../api/Users/Controller/UserController.java | 15 +++++ src/main/resources/application.properties | 9 +++ 6 files changed, 104 insertions(+) create mode 100644 src/main/java/smartpot/com/api/Cache/RedisConfig.java diff --git a/.env.example b/.env.example index c17126a..fe5afe2 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,16 @@ DATA_SOURCE_DOMAIN=domain DATA_SOURCE_DB=database DATA_PARAMS=params +# Redis Credentials +CACHE_TYPE=redis +CACHE_HOST=host +CACHE_PORT=port +CACHE_DB=id +CACHE_USERNAME=user +CACHE_PASSWORD=password +CACHE_TIMEOUT=ms +CACHE_MAXIMUMACTIVECONNECTIONCOUNT=max + # Email Credentials MAIL_HOST=SMTP.GMAIL.COM MAIL_PORT=587 diff --git a/pom.xml b/pom.xml index a79c9c5..8435e9e 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,16 @@ org.springframework.boot spring-boot-starter-data-mongodb + + org.springframework.boot + spring-boot-starter-data-redis + 3.4.1 + + + org.apache.commons + commons-pool2 + 2.11.1 + org.springframework.boot spring-boot-starter-validation diff --git a/src/main/java/smartpot/com/api/Cache/RedisConfig.java b/src/main/java/smartpot/com/api/Cache/RedisConfig.java new file mode 100644 index 0000000..058a15f --- /dev/null +++ b/src/main/java/smartpot/com/api/Cache/RedisConfig.java @@ -0,0 +1,58 @@ +package smartpot.com.api.Cache; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +import java.time.Duration; + +@Configuration +@EnableCaching +public class RedisConfig { + @Value(value="${spring.data.redis.host}") + private String host; + + @Value(value="${spring.data.redis.port}") + private String port; + + @Value("${spring.data.redis.username}") + private String username; + + @Value("${spring.data.redis.password}") + private String password; + + @Value("${spring.data.redis.database}") + private String database; + + @Value(value="${spring.data.redis.timeout}") + private String timeout; + + @Bean + JedisConnectionFactory jedisConnectionFactory() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); + redisStandaloneConfiguration.setUsername(username); + redisStandaloneConfiguration.setPassword(password); + redisStandaloneConfiguration.setDatabase(Integer.parseInt(database)); + redisStandaloneConfiguration.setHostName(host); + redisStandaloneConfiguration.setPort(Integer.valueOf(port)); + + JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder(); + jedisClientConfiguration.connectTimeout(Duration.ofSeconds(Integer.valueOf(timeout))); + + return new JedisConnectionFactory(redisStandaloneConfiguration, + jedisClientConfiguration.build()); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(jedisConnectionFactory()); + return template; + } +} diff --git a/src/main/java/smartpot/com/api/SmartPotApiApplication.java b/src/main/java/smartpot/com/api/SmartPotApiApplication.java index aebb0ee..29d4606 100644 --- a/src/main/java/smartpot/com/api/SmartPotApiApplication.java +++ b/src/main/java/smartpot/com/api/SmartPotApiApplication.java @@ -4,10 +4,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication @EnableMongoRepositories(basePackages = "smartpot.com.api") +@EnableCaching @Slf4j public class SmartPotApiApplication { diff --git a/src/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java index 2e025b5..f87804a 100644 --- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java +++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java @@ -8,6 +8,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -23,6 +27,7 @@ */ @RestController @RequestMapping("/Users") +@CacheConfig(cacheNames = "users") @Tag(name = "Usuarios", description = "Operaciones relacionadas con usuarios") public class UserController { @@ -67,6 +72,7 @@ public UserController(SUserI serviceUser) { * */ @PostMapping("/Create") + @CachePut(value = "users", key = "#userDTO.id") @Operation(summary = "Crear un nuevo usuario", description = "Crea un nuevo usuario con la información proporcionada. " + "El correo electrónico debe ser único y los campos obligatorios deben estar completos.", @@ -106,6 +112,7 @@ public ResponseEntity createUser( * */ @GetMapping("/All") + @Cacheable(value = "users", key = "'all_users'") @Operation(summary = "Obtener todos los usuarios", description = "Recupera todos los usuarios registrados en el sistema. " + "En caso de no haber usuarios, se devolverá una excepción.", @@ -147,6 +154,7 @@ public ResponseEntity getAllUsers() { * */ @GetMapping("/id/{id}") + @Cacheable(value = "users", key = "#id") @Operation(summary = "Buscar usuario por ID", description = "Recupera un usuario utilizando su ID único. " + "Si el usuario no existe, se devolverá un error con el código HTTP 404.", @@ -185,6 +193,7 @@ public ResponseEntity getUserById(@PathVariable @Parameter(description = "ID * */ @GetMapping("/email/{email}") + @Cacheable(value = "users", key = "#email") @Operation(summary = "Buscar usuario por correo electrónico", description = "Recupera un usuario utilizando su correo electrónico único. " + "Si no se encuentra el usuario, se devolverá un error.", @@ -224,6 +233,7 @@ public ResponseEntity getUsersByEmail(@Parameter(description = "Correo electr * */ @GetMapping("/name/{name}") + @Cacheable(value = "users", key = "#name") @Operation(summary = "Buscar usuarios por nombre", description = "Recupera usuarios cuyo nombre coincide con el proporcionado. " + "Si no se encuentran usuarios, se devolverá una lista vacía.", @@ -264,6 +274,7 @@ public ResponseEntity getUsersByName(@Parameter(description = "Nombre del usu * */ @GetMapping("/lastname/{lastname}") + @Cacheable(value = "users", key = "#lastname") @Operation(summary = "Buscar usuarios por apellido", description = "Recupera usuarios cuyo apellido coincide con el proporcionado. " + "Si no se encuentran usuarios, se devolverá una lista vacía.", @@ -303,6 +314,7 @@ public ResponseEntity getUsersByLastname(@Parameter(description = "Apellido d * */ @GetMapping("/role/{role}") + @Cacheable(value = "users", key = "#role") @Operation(summary = "Buscar usuarios por rol", description = "Recupera usuarios cuyo rol coincide con el proporcionado. " + "Si no se encuentran usuarios, se devolverá una lista vacía.", @@ -341,6 +353,7 @@ public ResponseEntity getUsersByRole(@Parameter(description = "Rol del usuari * */ @GetMapping("/role/All") + @Cacheable(value = "users", key = "'all_rols'") @Operation(summary = "Obtener todos los roles de usuario", description = "Recupera todos los roles de usuario registrados en el sistema. " + "En caso de no haber roles de usuario, se devolverá una excepción.", @@ -382,6 +395,7 @@ public ResponseEntity getAllRoles() { * */ @PutMapping("/Update/{id}") + @CachePut(value = "users", key = "#id") @Operation(summary = "Actualizar un usuario", description = "Actualiza los datos de un usuario existente utilizando su ID. " + "Si el usuario no existe o hay un error, se devolverá un error con código HTTP 404.", @@ -422,6 +436,7 @@ public ResponseEntity updateUser( * */ @DeleteMapping("/Delete/{id}") + @CacheEvict(cacheNames = "users", allEntries = true) @Operation(summary = "Eliminar un usuario", description = "Elimina un usuario existente utilizando su ID. " + "Si el usuario no existe o hay un error, se devolverá un error con código HTTP 404.", diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b1d96e0..c0da8f1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -14,6 +14,15 @@ application.security.jwt.secret-key=${SECURITY_JWT_SECRET_KEY} application.security.jwt.expiration=${SECURITY_JWT_EXPIRATION} application.security.public.routes=${SECURITY_PUBLIC_ROUTES} +# Conexión de Redis +spring.data.redis.host=${CACHE_HOST} +spring.data.redis.port=${CACHE_PORT} +spring.data.redis.database=${CACHE_DB} +spring.data.redis.username=${CACHE_USERNAME} +spring.data.redis.password=${CACHE_PASSWORD} +spring.data.redis.timeout=${CACHE_TIMEOUT} +redis.maximumActiveConnectionCount=${CACHE_MAXIMUMACTIVECONNECTIONCOUNT} + # Swagger Config springdoc.api-docs.enabled=true springdoc.swagger-ui.enabled=true From e7ab6b34acf85a95d5e45173479ab5eb56a35728 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Thu, 2 Jan 2025 23:29:01 -0500 Subject: [PATCH 13/25] attempted redis configuration solution and asynchronicity in the same environment --- .../com/api/Mail/Config/AsyncConfig.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java index e64b8e5..9bed973 100644 --- a/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java +++ b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java @@ -1,5 +1,7 @@ package smartpot.com.api.Mail.Config; +import jakarta.annotation.PreDestroy; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.Async; @@ -67,4 +69,24 @@ public TaskExecutor taskExecutor() { executor.initialize(); return executor; } + + /** + * Método de cierre que se invoca al destruir la aplicación. + * Asegura que el {@link ThreadPoolTaskExecutor} se detenga correctamente, evitando fugas de memoria. + */ + @PreDestroy + public void shutdownExecutor() { + ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) taskExecutor(); + if (executor != null) { + executor.shutdown(); + try { + if (!executor.getThreadPoolExecutor().awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) { + executor.getThreadPoolExecutor().shutdownNow(); + } + } catch (InterruptedException e) { + executor.getThreadPoolExecutor().shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } } From a51586a51283837ff06965f9d75db48e0bd816d3 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Fri, 10 Jan 2025 14:00:44 -0500 Subject: [PATCH 14/25] Documentation of dependencies, under categorization --- pom.xml | 185 +++++++++++++----- .../smartpot/com/api/Cache/RedisConfig.java | 11 +- 2 files changed, 138 insertions(+), 58 deletions(-) diff --git a/pom.xml b/pom.xml index 8435e9e..9571b51 100644 --- a/pom.xml +++ b/pom.xml @@ -2,142 +2,195 @@ + 4.0.0 + + org.springframework.boot spring-boot-starter-parent 3.4.1 + + smarpot.com api 0.0.1-SNAPSHOT SmartPot-API SmartPot-API + + + + + + + + + 17 + + + org.springframework.boot - spring-boot-starter-actuator + spring-boot-starter-actuator org.springframework.boot - spring-boot-starter-data-mongodb + spring-boot-starter-web + org.springframework.boot - spring-boot-starter-data-redis - 3.4.1 + spring-boot-starter-validation + + - org.apache.commons - commons-pool2 - 2.11.1 + org.springframework.boot + spring-boot-starter-data-mongodb + org.springframework.boot - spring-boot-starter-validation + spring-boot-starter-data-redis + 3.4.1 + - org.springframework.boot - spring-boot-devtools - runtime - true + org.springframework.data + spring-data-redis + 3.4.1 + - org.springframework.boot - spring-boot-starter-test - test + redis.clients + jedis + 4.0.0 + + org.springframework.boot - spring-boot-starter-web + spring-boot-starter-security + - org.projectlombok - lombok - compile + org.springframework.security + spring-security-test + test + + - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.7.0 + io.jsonwebtoken + jjwt + 0.12.6 + - org.springframework.restdocs - spring-restdocs-mockmvc - test + io.jsonwebtoken + jjwt-impl + 0.12.6 + + + io.jsonwebtoken + jjwt-jackson + 0.12.6 + + + org.springframework.boot - spring-boot-starter-security + spring-boot-starter-test + test + - org.springframework.security - spring-security-test - test + org.springframework.restdocs + spring-restdocs-mockmvc + test - + - io.jsonwebtoken - jjwt - 0.12.6 + org.springframework.boot + spring-boot-devtools + runtime + true - io.jsonwebtoken - jjwt-impl - 0.12.6 + org.projectlombok + lombok + compile - io.jsonwebtoken - jjwt-jackson - 0.12.6 + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.7.0 + io.github.cdimascio dotenv-java - 3.1.0 + 3.1.0 + org.mapstruct mapstruct - 1.6.3 + 1.6.3 + org.mapstruct mapstruct-processor - 1.6.3 + 1.6.3 + org.springframework.boot - spring-boot-starter-cache + spring-boot-starter-cache + + org.springframework.boot - spring-boot-starter-mail + spring-boot-starter-mail + + + + org.apache.commons + commons-pool2 + 2.11.1 + + + @@ -146,21 +199,47 @@ + + + docker - - - src/main/resources - - .env - - - + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + + copy-resources + + + ${project.build.directory}/docker/resources + + + + src/main/resources + false + + + + ${basedir} + + .env + + + + + + + + - \ No newline at end of file diff --git a/src/main/java/smartpot/com/api/Cache/RedisConfig.java b/src/main/java/smartpot/com/api/Cache/RedisConfig.java index 058a15f..06b1876 100644 --- a/src/main/java/smartpot/com/api/Cache/RedisConfig.java +++ b/src/main/java/smartpot/com/api/Cache/RedisConfig.java @@ -5,6 +5,7 @@ import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @@ -36,14 +37,14 @@ public class RedisConfig { @Bean JedisConnectionFactory jedisConnectionFactory() { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); - redisStandaloneConfiguration.setUsername(username); - redisStandaloneConfiguration.setPassword(password); - redisStandaloneConfiguration.setDatabase(Integer.parseInt(database)); redisStandaloneConfiguration.setHostName(host); - redisStandaloneConfiguration.setPort(Integer.valueOf(port)); + redisStandaloneConfiguration.setPort(Integer.parseInt(port)); + redisStandaloneConfiguration.setDatabase(Integer.parseInt(database)); + redisStandaloneConfiguration.setUsername(username); + redisStandaloneConfiguration.setPassword(RedisPassword.of(password)); JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder(); - jedisClientConfiguration.connectTimeout(Duration.ofSeconds(Integer.valueOf(timeout))); + jedisClientConfiguration.connectTimeout(Duration.ofSeconds(Long.parseLong(timeout))); return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration.build()); From a8dafa11ed64cf253826e162f4adce9e5d812d65 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Fri, 10 Jan 2025 22:54:24 -0500 Subject: [PATCH 15/25] Implementation of a cache system in the user management service --- pom.xml | 8 +--- .../smartpot/com/api/Cache/RedisConfig.java | 47 +++++++++++++++---- .../com/api/Responses/CResponseEntity.java | 29 ++++++++++++ .../api/Users/Controller/UserController.java | 11 +---- .../api/Users/Model/DAO/Service/SUser.java | 13 +++++ .../com/api/Users/Model/DTO/UserDTO.java | 4 +- src/main/resources/application.properties | 12 ++++- 7 files changed, 96 insertions(+), 28 deletions(-) create mode 100644 src/main/java/smartpot/com/api/Responses/CResponseEntity.java diff --git a/pom.xml b/pom.xml index 9571b51..4170573 100644 --- a/pom.xml +++ b/pom.xml @@ -76,16 +76,10 @@ 3.4.1 - - org.springframework.data - spring-data-redis - 3.4.1 - - redis.clients jedis - 4.0.0 + 5.2.0 diff --git a/src/main/java/smartpot/com/api/Cache/RedisConfig.java b/src/main/java/smartpot/com/api/Cache/RedisConfig.java index 06b1876..b61c40d 100644 --- a/src/main/java/smartpot/com/api/Cache/RedisConfig.java +++ b/src/main/java/smartpot/com/api/Cache/RedisConfig.java @@ -1,15 +1,23 @@ package smartpot.com.api.Cache; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @@ -31,11 +39,23 @@ public class RedisConfig { @Value("${spring.data.redis.database}") private String database; - @Value(value="${spring.data.redis.timeout}") - private String timeout; + @Value("${spring.data.redis.timeout}") + private long timeout; + + @Value("${CACHE_LETTUCE_POOL_MAX_ACTIVE}") + private int maxActive; + + @Value("${CACHE_LETTUCE_POOL_MAX_WAIT}") + private long maxWaitMillis; + + @Value("${CACHE_LETTUCE_POOL_MAX_IDLE}") + private int maxIdle; + + @Value("${CACHE_LETTUCE_POOL_MIN_IDLE}") + private int minIdle; @Bean - JedisConnectionFactory jedisConnectionFactory() { + public LettuceConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(Integer.parseInt(port)); @@ -43,17 +63,26 @@ JedisConnectionFactory jedisConnectionFactory() { redisStandaloneConfiguration.setUsername(username); redisStandaloneConfiguration.setPassword(RedisPassword.of(password)); - JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder(); - jedisClientConfiguration.connectTimeout(Duration.ofSeconds(Long.parseLong(timeout))); + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>(); + poolConfig.setMaxTotal(maxActive); + poolConfig.setMaxIdle(maxIdle); + poolConfig.setMinIdle(minIdle); + poolConfig.setBlockWhenExhausted(true); + poolConfig.setMaxWait(Duration.ofMillis(maxWaitMillis)); + + LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() + .poolConfig(poolConfig) + .commandTimeout(Duration.ofMillis(timeout)) + .shutdownTimeout(Duration.ofMillis(timeout)) + .build(); - return new JedisConnectionFactory(redisStandaloneConfiguration, - jedisClientConfiguration.build()); + return new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig); } @Bean - public RedisTemplate redisTemplate() { + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(jedisConnectionFactory()); + template.setConnectionFactory(factory); return template; } } diff --git a/src/main/java/smartpot/com/api/Responses/CResponseEntity.java b/src/main/java/smartpot/com/api/Responses/CResponseEntity.java new file mode 100644 index 0000000..fed92fc --- /dev/null +++ b/src/main/java/smartpot/com/api/Responses/CResponseEntity.java @@ -0,0 +1,29 @@ +package smartpot.com.api.Responses; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; + +import java.io.Serial; +import java.io.Serializable; + +public class CResponseEntity extends ResponseEntity implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + public CResponseEntity() { + super((T) null, HttpStatus.OK); + } + + public CResponseEntity(T body, HttpStatus status) { + super(body, status); + } + + public CResponseEntity(MultiValueMap headers, HttpStatus status) { + super(headers, status); + } + + public CResponseEntity(T body, MultiValueMap headers, HttpStatus status) { + super(body, headers, status); + } +} diff --git a/src/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java index f87804a..faff316 100644 --- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java +++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java @@ -15,6 +15,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import smartpot.com.api.Responses.CResponseEntity; import smartpot.com.api.Responses.DeleteResponse; import smartpot.com.api.Responses.ErrorResponse; import smartpot.com.api.Users.Model.DAO.Service.SUserI; @@ -72,7 +73,6 @@ public UserController(SUserI serviceUser) { * */ @PostMapping("/Create") - @CachePut(value = "users", key = "#userDTO.id") @Operation(summary = "Crear un nuevo usuario", description = "Crea un nuevo usuario con la información proporcionada. " + "El correo electrónico debe ser único y los campos obligatorios deben estar completos.", @@ -112,7 +112,6 @@ public ResponseEntity createUser( * */ @GetMapping("/All") - @Cacheable(value = "users", key = "'all_users'") @Operation(summary = "Obtener todos los usuarios", description = "Recupera todos los usuarios registrados en el sistema. " + "En caso de no haber usuarios, se devolverá una excepción.", @@ -154,7 +153,6 @@ public ResponseEntity getAllUsers() { * */ @GetMapping("/id/{id}") - @Cacheable(value = "users", key = "#id") @Operation(summary = "Buscar usuario por ID", description = "Recupera un usuario utilizando su ID único. " + "Si el usuario no existe, se devolverá un error con el código HTTP 404.", @@ -193,7 +191,6 @@ public ResponseEntity getUserById(@PathVariable @Parameter(description = "ID * */ @GetMapping("/email/{email}") - @Cacheable(value = "users", key = "#email") @Operation(summary = "Buscar usuario por correo electrónico", description = "Recupera un usuario utilizando su correo electrónico único. " + "Si no se encuentra el usuario, se devolverá un error.", @@ -233,7 +230,6 @@ public ResponseEntity getUsersByEmail(@Parameter(description = "Correo electr * */ @GetMapping("/name/{name}") - @Cacheable(value = "users", key = "#name") @Operation(summary = "Buscar usuarios por nombre", description = "Recupera usuarios cuyo nombre coincide con el proporcionado. " + "Si no se encuentran usuarios, se devolverá una lista vacía.", @@ -274,7 +270,6 @@ public ResponseEntity getUsersByName(@Parameter(description = "Nombre del usu * */ @GetMapping("/lastname/{lastname}") - @Cacheable(value = "users", key = "#lastname") @Operation(summary = "Buscar usuarios por apellido", description = "Recupera usuarios cuyo apellido coincide con el proporcionado. " + "Si no se encuentran usuarios, se devolverá una lista vacía.", @@ -314,7 +309,6 @@ public ResponseEntity getUsersByLastname(@Parameter(description = "Apellido d * */ @GetMapping("/role/{role}") - @Cacheable(value = "users", key = "#role") @Operation(summary = "Buscar usuarios por rol", description = "Recupera usuarios cuyo rol coincide con el proporcionado. " + "Si no se encuentran usuarios, se devolverá una lista vacía.", @@ -353,7 +347,6 @@ public ResponseEntity getUsersByRole(@Parameter(description = "Rol del usuari * */ @GetMapping("/role/All") - @Cacheable(value = "users", key = "'all_rols'") @Operation(summary = "Obtener todos los roles de usuario", description = "Recupera todos los roles de usuario registrados en el sistema. " + "En caso de no haber roles de usuario, se devolverá una excepción.", @@ -395,7 +388,6 @@ public ResponseEntity getAllRoles() { * */ @PutMapping("/Update/{id}") - @CachePut(value = "users", key = "#id") @Operation(summary = "Actualizar un usuario", description = "Actualiza los datos de un usuario existente utilizando su ID. " + "Si el usuario no existe o hay un error, se devolverá un error con código HTTP 404.", @@ -436,7 +428,6 @@ public ResponseEntity updateUser( * */ @DeleteMapping("/Delete/{id}") - @CacheEvict(cacheNames = "users", allEntries = true) @Operation(summary = "Eliminar un usuario", description = "Elimina un usuario existente utilizando su ID. " + "Si el usuario no existe o hay un error, se devolverá un error con código HTTP 404.", diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index 3c2bba0..82bcfb0 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -6,6 +6,9 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @@ -67,6 +70,7 @@ public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { * @see ValidationException */ @Override + @CachePut(value = "users", key = "#userDTO.id") public UserDTO CreateUser(UserDTO userDTO) throws Exception { return Optional.of(userDTO) .filter(dto -> !repositoryUser.existsByEmail(dto.getEmail())) @@ -106,6 +110,7 @@ public UserDTO CreateUser(UserDTO userDTO) throws Exception { * @see UserDTO */ @Override + @Cacheable(value = "users", key = "'all_users'") public List getAllUsers() throws Exception { return Optional.of(repositoryUser.findAll()) .filter(users -> !users.isEmpty()) @@ -132,6 +137,7 @@ public List getAllUsers() throws Exception { * @see ValidationException */ @Override + @Cacheable(value = "users", key = "#id") public UserDTO getUserById(String id) throws Exception { return Optional.of(id) .map(ValidId -> { @@ -167,6 +173,7 @@ public UserDTO getUserById(String id) throws Exception { * @see ValidationException */ @Override + @Cacheable(value = "users", key = "#email") public UserDTO getUserByEmail(String email) throws Exception { return Optional.of(email) .map(ValidEmail -> { @@ -202,6 +209,7 @@ public UserDTO getUserByEmail(String email) throws Exception { * @see ValidationException */ @Override + @Cacheable(value = "users", key = "#name") public List getUsersByName(String name) throws Exception { return Optional.of(name) .map(ValidName -> { @@ -238,6 +246,7 @@ public List getUsersByName(String name) throws Exception { * @see ValidationException */ @Override + @Cacheable(value = "users", key = "#lastname") public List getUsersByLastname(String lastname) throws Exception { return Optional.of(lastname) .map(ValidLastname -> { @@ -274,6 +283,7 @@ public List getUsersByLastname(String lastname) throws Exception { * @see ValidationException */ @Override + @Cacheable(value = "users", key = "#role") public List getUsersByRole(String role) throws Exception { return Optional.of(role) .map(ValidRole -> { @@ -303,6 +313,7 @@ public List getUsersByRole(String role) throws Exception { * @see Role */ @Override + @Cacheable(value = "users", key = "'all_rols'") public List getAllRoles() throws Exception { return Optional.of(Role.getRoleNames()) .filter(roles -> !roles.isEmpty()) @@ -327,6 +338,7 @@ public List getAllRoles() throws Exception { * @see ValidationException */ @Override + @CachePut(value = "users", key = "#id") public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { UserDTO existingUser = getUserById(id); return Optional.of(updatedUser) @@ -371,6 +383,7 @@ public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { * @see UserDTO */ @Override + @CacheEvict(cacheNames = "users", allEntries = true) public String DeleteUser(String id) throws Exception { return Optional.of(getUserById(id)) .map(user -> { diff --git a/src/main/java/smartpot/com/api/Users/Model/DTO/UserDTO.java b/src/main/java/smartpot/com/api/Users/Model/DTO/UserDTO.java index 6ebb6d5..ef3b8c0 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DTO/UserDTO.java +++ b/src/main/java/smartpot/com/api/Users/Model/DTO/UserDTO.java @@ -4,9 +4,11 @@ import lombok.Data; import lombok.RequiredArgsConstructor; +import java.io.Serializable; + @Data @RequiredArgsConstructor -public class UserDTO { +public class UserDTO implements Serializable { @Schema(description = "ID único del usuario, generado automáticamente por la base de datos.", example = "676ae2a9b909de5f9607fcb6", hidden = true) private String id = null; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c0da8f1..ac2a7dc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -21,7 +21,17 @@ spring.data.redis.database=${CACHE_DB} spring.data.redis.username=${CACHE_USERNAME} spring.data.redis.password=${CACHE_PASSWORD} spring.data.redis.timeout=${CACHE_TIMEOUT} -redis.maximumActiveConnectionCount=${CACHE_MAXIMUMACTIVECONNECTIONCOUNT} +spring.data.redis.lettuce.pool.max-active=${CACHE_LETTUCE_POOL_MAX_ACTIVE} +spring.data.redis.lettuce.pool.max-wait=${CACHE_LETTUCE_POOL_MAX_WAIT} +spring.data.redis.lettuce.pool.max-idle=${CACHE_LETTUCE_POOL_MAX_IDLE} +spring.data.redis.lettuce.pool.min-idle=${CACHE_LETTUCE_POOL_MIN_IDLE} + +# Cache Config +spring.cache.type=${CACHE_TYPE} +spring.cache.redis.time-to-live=${CACHE_TIME_TO_LIVE} +spring.cache.redis.cache-null-values=${CACHE_NULL_VALUES} + + # Swagger Config springdoc.api-docs.enabled=true From aec1cfb369437f4c60eba462e5d3b3e2c54ed129 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Fri, 10 Jan 2025 22:55:52 -0500 Subject: [PATCH 16/25] Reformat Code --- .../smartpot/com/api/Cache/RedisConfig.java | 10 ++----- .../api/Crops/Controller/CropController.java | 4 +-- .../api/Crops/Model/DAO/Service/SCrop.java | 24 +++++---------- .../com/api/Crops/Model/DTO/CropDTO.java | 4 ++- .../com/api/Mail/Config/AsyncConfig.java | 5 ++-- .../Mail/Model/DAO/Service/EmailService.java | 10 +++---- .../com/api/Responses/CResponseEntity.java | 29 ------------------- .../Security/Controller/AuthController.java | 5 ++-- .../api/Security/Filter/JwtAuthFilter.java | 3 +- .../com/api/Security/Service/JwtService.java | 12 ++++---- .../com/api/Security/Service/JwtServiceI.java | 3 -- .../api/Users/Controller/UserController.java | 5 +--- .../api/Users/Model/DAO/Service/SUser.java | 29 +++++++------------ .../api/Users/Model/DAO/Service/SUserI.java | 2 +- .../com/api/Users/Validator/VUser.java | 8 +++-- 15 files changed, 49 insertions(+), 104 deletions(-) delete mode 100644 src/main/java/smartpot/com/api/Responses/CResponseEntity.java diff --git a/src/main/java/smartpot/com/api/Cache/RedisConfig.java b/src/main/java/smartpot/com/api/Cache/RedisConfig.java index b61c40d..f8ef184 100644 --- a/src/main/java/smartpot/com/api/Cache/RedisConfig.java +++ b/src/main/java/smartpot/com/api/Cache/RedisConfig.java @@ -2,7 +2,6 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,24 +9,19 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @Configuration @EnableCaching public class RedisConfig { - @Value(value="${spring.data.redis.host}") + @Value(value = "${spring.data.redis.host}") private String host; - @Value(value="${spring.data.redis.port}") + @Value(value = "${spring.data.redis.port}") private String port; @Value("${spring.data.redis.username}") diff --git a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java index 50be387..7f27495 100644 --- a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java +++ b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java @@ -393,8 +393,8 @@ public ResponseEntity countCropsByUser(@PathVariable @Parameter(description = * Si el cultivo con el ID proporcionado existe, se actualizarán sus detalles con la información proporcionada en el objeto {@link CropDTO}.

*

Si el cultivo no existe o si ocurre algún error durante el proceso de actualización, se devolverá un mensaje de error con el código HTTP 404.

* - * @param id El identificador único del cultivo que se desea actualizar. Este parámetro es obligatorio para identificar el cultivo en la base de datos. - * El ID debe ser válido y hacer referencia a un cultivo existente. + * @param id El identificador único del cultivo que se desea actualizar. Este parámetro es obligatorio para identificar el cultivo en la base de datos. + * El ID debe ser válido y hacer referencia a un cultivo existente. * @param cropDetails El objeto {@link CropDTO} que contiene los nuevos datos del cultivo que se desean actualizar. Este objeto debe incluir toda la información que reemplazará los detalles actuales del cultivo. * @return Un objeto {@link ResponseEntity} que contiene: *
    diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index 2d13a72..c9467c1 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -14,6 +14,7 @@ import smartpot.com.api.Crops.Model.Entity.Type; import smartpot.com.api.Crops.Validator.VCropI; import smartpot.com.api.Users.Model.DAO.Service.SUserI; + import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -87,7 +88,7 @@ public CropDTO createCrop(CropDTO cropDTO) throws Exception { try { serviceUser.getUserById(ValidCropDTO.getUser()); } catch (Exception e) { - throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente"); + throw new ValidationException(e.getMessage() + ", asocia el cultivo a un usuario existente"); } if (!validatorCrop.isValid()) { @@ -115,7 +116,6 @@ public CropDTO createCrop(CropDTO cropDTO) throws Exception { * * @return Una lista de objetos {@link CropDTO} que representan todos los cultivos en la base de datos. * @throws Exception Si no existen cultivos registrados en la base de datos. - * * @see CropDTO * @see RCrop * @see MCrop @@ -141,9 +141,8 @@ public List getAllCrops() throws Exception { * * @param id El identificador del cultivo que se desea obtener. El ID debe ser una cadena que representa un {@link ObjectId}. * @return Un objeto {@link CropDTO} que representa el cultivo encontrado. - * @throws Exception Si el cultivo no existe en la base de datos o si el ID no es válido. + * @throws Exception Si el cultivo no existe en la base de datos o si el ID no es válido. * @throws ValidationException Si el ID proporcionado no es válido según las reglas de validación del validador {@link VCropI}. - * * @see CropDTO * @see VCropI * @see RCrop @@ -179,7 +178,6 @@ public CropDTO getCropById(String id) throws Exception { * @param id El identificador del usuario cuyos cultivos se desean obtener. El ID debe ser una cadena que representa un {@link ObjectId}. * @return Una lista de objetos {@link CropDTO} que representan los cultivos asociados al usuario. * @throws Exception Si el usuario no tiene cultivos o si el ID del usuario es inválido o no existe. - * * @see CropDTO * @see SUserI * @see RCrop @@ -225,9 +223,8 @@ public long countCropsByUser(String id) throws Exception { * * @param type El tipo de cultivo que se desea obtener. * @return Una lista de objetos {@link CropDTO} que representan los cultivos encontrados con el tipo proporcionado. - * @throws Exception Si no se encuentran cultivos con el tipo proporcionado o si ocurre algún otro error. + * @throws Exception Si no se encuentran cultivos con el tipo proporcionado o si ocurre algún otro error. * @throws ValidationException Si el tipo de cultivo proporcionado no es válido según las reglas de validación del validador {@link VCropI}. - * * @see CropDTO * @see VCropI * @see RCrop @@ -260,7 +257,6 @@ public List getCropsByType(String type) throws Exception { * * @return Una lista de cadenas {@link String} que representan los nombres de los tipos de cultivo encontrados. * @throws Exception Si ocurre un error al obtener los tipos de cultivo o si no se encuentran tipos registrados. - * * @see String * @see Type */ @@ -283,9 +279,8 @@ public List getAllTypes() throws Exception { * * @param status El estado del cultivo que se desea obtener. * @return Una lista de objetos {@link CropDTO} que representan los cultivos encontrados con el estado proporcionado. - * @throws Exception Si no se encuentran cultivos con el estado proporcionado o si ocurre algún otro error. + * @throws Exception Si no se encuentran cultivos con el estado proporcionado o si ocurre algún otro error. * @throws ValidationException Si el estado proporcionado no es válido según las reglas de validación del validador {@link VCropI}. - * * @see CropDTO * @see VCropI * @see RCrop @@ -318,7 +313,6 @@ public List getCropsByStatus(String status) throws Exception { * * @return Una lista de cadenas {@link String} que representan los estados de cultivo encontrados. * @throws Exception Si ocurre un error al buscar los estados de cultivo o si no se encuentran estados registrados. - * * @see String * @see Status */ @@ -341,13 +335,12 @@ public List getAllStatus() throws Exception { *

    Si las validaciones no pasan o si ocurre un error durante el proceso de actualización, se lanza una * {@link ValidationException} o una {@link Exception}, respectivamente.

    * - * @param id El ID único del cultivo que se desea actualizar. Este parámetro es obligatorio. + * @param id El ID único del cultivo que se desea actualizar. Este parámetro es obligatorio. * @param updateCrop El objeto {@link CropDTO} que contiene los nuevos datos para actualizar el cultivo. * Los campos nulos no modificarán el valor existente. * @return El objeto {@link CropDTO} actualizado que representa el cultivo después de la actualización. - * @throws Exception Si ocurre un error al intentar actualizar el cultivo, como si el cultivo no existe. + * @throws Exception Si ocurre un error al intentar actualizar el cultivo, como si el cultivo no existe. * @throws ValidationException Si alguna de las validaciones del estado, tipo o usuario falla según el validador {@link VCropI}. - * * @see CropDTO * @see VCropI * @see RCrop @@ -369,7 +362,7 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { try { serviceUser.getUserById(existingCrop.getId()); } catch (Exception e) { - throw new ValidationException(e.getMessage()+", asocia el cultivo a un usuario existente"); + throw new ValidationException(e.getMessage() + ", asocia el cultivo a un usuario existente"); } if (!validatorCrop.isValid()) { throw new ValidationException(validatorCrop.getErrors().toString()); @@ -396,7 +389,6 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { * @param id El ID único del cultivo que se desea eliminar. * @return Un mensaje de confirmación que indica que el cultivo con el ID proporcionado fue eliminado correctamente. * @throws Exception Si el cultivo no existe en la base de datos o si ocurre algún otro error durante la eliminación. - * * @see RCrop */ @Override diff --git a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java index 456d558..38adc8d 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java @@ -1,7 +1,9 @@ package smartpot.com.api.Crops.Model.DTO; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; /** diff --git a/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java index 9bed973..33ec504 100644 --- a/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java +++ b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java @@ -1,13 +1,12 @@ package smartpot.com.api.Mail.Config; import jakarta.annotation.PreDestroy; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.core.task.TaskExecutor; /** * Clase de configuración para habilitar y gestionar la ejecución asincrónica en la aplicación. diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java index c6ea31f..ee52f61 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java @@ -1,6 +1,5 @@ package smartpot.com.api.Mail.Model.DAO.Service; -import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -25,12 +24,11 @@ @Slf4j @Service public class EmailService implements EmailServiceI { - @Value("${spring.mail.username}") - private String sender; - private final JavaMailSender javaMailSender; private final EmailRepository emailRepository; private final EmailMapper emailMapper; + @Value("${spring.mail.username}") + private String sender; @Autowired public EmailService(JavaMailSender javaMailSender, EmailRepository emailRepository, EmailMapper emailMapper) { @@ -52,7 +50,7 @@ public void sendSimpleMail(EmailDetails details) { emailRepository.save(details); log.warn("Correo Enviado Exitosamente"); } catch (Exception e) { - log.error("Error al Enviar Correo "+e.getMessage()); + log.error("Error al Enviar Correo " + e.getMessage()); } } @@ -73,7 +71,7 @@ public void sendMailWithAttachment(EmailDetails details) { emailRepository.save(details); log.warn("Correo Enviado Exitosamente"); } catch (Exception e) { - log.error("Error al Enviar Correo "+e.getMessage()); + log.error("Error al Enviar Correo " + e.getMessage()); } } diff --git a/src/main/java/smartpot/com/api/Responses/CResponseEntity.java b/src/main/java/smartpot/com/api/Responses/CResponseEntity.java deleted file mode 100644 index fed92fc..0000000 --- a/src/main/java/smartpot/com/api/Responses/CResponseEntity.java +++ /dev/null @@ -1,29 +0,0 @@ -package smartpot.com.api.Responses; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.MultiValueMap; - -import java.io.Serial; -import java.io.Serializable; - -public class CResponseEntity extends ResponseEntity implements Serializable { - @Serial - private static final long serialVersionUID = 1L; - - public CResponseEntity() { - super((T) null, HttpStatus.OK); - } - - public CResponseEntity(T body, HttpStatus status) { - super(body, status); - } - - public CResponseEntity(MultiValueMap headers, HttpStatus status) { - super(headers, status); - } - - public CResponseEntity(T body, MultiValueMap headers, HttpStatus status) { - super(body, headers, status); - } -} diff --git a/src/main/java/smartpot/com/api/Security/Controller/AuthController.java b/src/main/java/smartpot/com/api/Security/Controller/AuthController.java index c4f6552..2720cff 100644 --- a/src/main/java/smartpot/com/api/Security/Controller/AuthController.java +++ b/src/main/java/smartpot/com/api/Security/Controller/AuthController.java @@ -79,10 +79,9 @@ public ResponseEntity login(@RequestBody UserDTO reqUser) { public ResponseEntity verify(@RequestHeader("Authorization") String authHeader) { try { return new ResponseEntity<>(jwtService.validateAuthHeader(authHeader), HttpStatus.OK); - }catch (InvalidTokenException e) { + } catch (InvalidTokenException e) { return new ResponseEntity<>(new ErrorResponse("Token Invalido [" + e.getMessage() + "]", HttpStatus.I_AM_A_TEAPOT.value()), HttpStatus.I_AM_A_TEAPOT); - } - catch (Exception e) { + } catch (Exception e) { return new ResponseEntity<>(new ErrorResponse("Error al verificar token [" + e.getMessage() + "]", HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST); } diff --git a/src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java b/src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java index 1b5012d..1ae407c 100644 --- a/src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java +++ b/src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java @@ -43,7 +43,8 @@ protected void doFilterInternal( user, user.getPassword(), null /* user.getAuthorities() */); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } filterChain.doFilter(request, response); } } \ No newline at end of file diff --git a/src/main/java/smartpot/com/api/Security/Service/JwtService.java b/src/main/java/smartpot/com/api/Security/Service/JwtService.java index ba6bf1a..5613723 100644 --- a/src/main/java/smartpot/com/api/Security/Service/JwtService.java +++ b/src/main/java/smartpot/com/api/Security/Service/JwtService.java @@ -24,15 +24,13 @@ @Service public class JwtService implements JwtServiceI { + private final SUserI serviceUser; + private final EmailServiceI emailService; @Value("${application.security.jwt.secret-key}") private String secretKey; - @Value("${application.security.jwt.expiration}") private long expiration; - private final SUserI serviceUser; - private final EmailServiceI emailService; - /** * Constructor que inyecta las dependencias del servicio. * @@ -47,18 +45,18 @@ public JwtService(SUserI serviceUser, EmailServiceI emailService) { @Override public String Login(UserDTO reqUser) throws Exception { return Optional.of(serviceUser.getUserByEmail(reqUser.getEmail())) - .filter( userDTO -> new BCryptPasswordEncoder().matches(reqUser.getPassword(), userDTO.getPassword())) + .filter(userDTO -> new BCryptPasswordEncoder().matches(reqUser.getPassword(), userDTO.getPassword())) .map(validUser -> generateToken(validUser.getId(), validUser.getEmail())) .map(validToken -> { emailService.sendSimpleMail( new EmailDetails( null, "smartpottech@gmail.com", - "Se ha iniciado sesion en su cuenta, verifique su token de seguridad '"+validToken+"'", + "Se ha iniciado sesion en su cuenta, verifique su token de seguridad '" + validToken + "'", "Inicio de Sesion en Smartpot", "" )); - return validToken; + return validToken; }) .orElseThrow(() -> new Exception("Credenciales Invalidas")); diff --git a/src/main/java/smartpot/com/api/Security/Service/JwtServiceI.java b/src/main/java/smartpot/com/api/Security/Service/JwtServiceI.java index 8c39acb..5b815bc 100644 --- a/src/main/java/smartpot/com/api/Security/Service/JwtServiceI.java +++ b/src/main/java/smartpot/com/api/Security/Service/JwtServiceI.java @@ -1,10 +1,7 @@ package smartpot.com.api.Security.Service; -import org.springframework.security.core.userdetails.UserDetails; import smartpot.com.api.Users.Model.DTO.UserDTO; -import java.util.Date; - public interface JwtServiceI { String Login(UserDTO reqUser) throws Exception; diff --git a/src/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java index faff316..4c8b5ee 100644 --- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java +++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java @@ -9,13 +9,9 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import smartpot.com.api.Responses.CResponseEntity; import smartpot.com.api.Responses.DeleteResponse; import smartpot.com.api.Responses.ErrorResponse; import smartpot.com.api.Users.Model.DAO.Service.SUserI; @@ -24,6 +20,7 @@ /** * Controlador REST para las operaciones relacionadas con los usuarios. *

    Este controlador proporciona una serie de métodos para gestionar usuarios en el sistema.

    + * * @see SUserI */ @RestController diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index 82bcfb0..70cd679 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -42,7 +42,7 @@ public class SUser implements SUserI { * Constructor que inyecta las dependencias del servicio. * * @param repositoryUser repositorio que maneja las operaciones de base de datos. - * @param mapperUser convertidor que convierte entidades User a UserDTO. + * @param mapperUser convertidor que convierte entidades User a UserDTO. * @param validatorUser validador que valida los datos de usuario. */ @Autowired @@ -61,11 +61,12 @@ public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { * se guarda en la base de datos. Si el usuario ya existe o si las validaciones fallan, se lanza una * excepción correspondiente. * * + * * @param userDTO el objeto {@link UserDTO} que contiene los datos del nuevo usuario. * @return un objeto {@link UserDTO} que representa al usuario creado. - * @throws Exception si el usuario ya existe en la base de datos (por email) o si hay un error de validación. + * @throws Exception si el usuario ya existe en la base de datos (por email) o si hay un error de validación. * @throws ValidationException si las validaciones de los campos del usuario no son exitosas. - * * + * * * @see UserDTO * @see ValidationException */ @@ -130,9 +131,8 @@ public List getAllUsers() throws Exception { * * @param id el ID del usuario que se desea obtener. El ID debe ser una cadena que representa un {@link ObjectId}. * @return un objeto {@link UserDTO} que representa al usuario encontrado. - * @throws Exception si el usuario no existe en la base de datos o si el ID no es válido. + * @throws Exception si el usuario no existe en la base de datos o si el ID no es válido. * @throws ValidationException si el ID proporcionado no es válido según las reglas de validación. - * * @see UserDTO * @see ValidationException */ @@ -166,9 +166,8 @@ public UserDTO getUserById(String id) throws Exception { * * @param email el correo electrónico del usuario que se desea obtener. * @return un objeto {@link UserDTO} que representa al usuario encontrado. - * @throws Exception si el usuario no existe en la base de datos o si el correo no es válido. + * @throws Exception si el usuario no existe en la base de datos o si el correo no es válido. * @throws ValidationException si el correo electrónico proporcionado no es válido según las reglas de validación. - * * @see UserDTO * @see ValidationException */ @@ -202,9 +201,8 @@ public UserDTO getUserByEmail(String email) throws Exception { * * @param name el nombre del usuario que se desea obtener. * @return una lista de objetos {@link UserDTO} que representan a los usuarios encontrados. - * @throws Exception si no existen usuarios con el nombre proporcionado o si el nombre no es válido. + * @throws Exception si no existen usuarios con el nombre proporcionado o si el nombre no es válido. * @throws ValidationException si el nombre proporcionado no es válido según las reglas de validación. - * * @see UserDTO * @see ValidationException */ @@ -239,9 +237,8 @@ public List getUsersByName(String name) throws Exception { * * @param lastname el apellido del usuario o los usuarios que se desea obtener. * @return una lista de objetos {@link UserDTO} que representan a los usuarios encontrados. - * @throws Exception si no existen usuarios con el apellido proporcionado o si el apellido no es válido. + * @throws Exception si no existen usuarios con el apellido proporcionado o si el apellido no es válido. * @throws ValidationException si el apellido proporcionado no es válido según las reglas de validación. - * * @see UserDTO * @see ValidationException */ @@ -276,9 +273,8 @@ public List getUsersByLastname(String lastname) throws Exception { * * @param role el rol del usuario o los usuarios que se desea obtener. * @return una lista de objetos {@link UserDTO} que representan a los usuarios encontrados. - * @throws Exception si no existen usuarios con el rol proporcionado o si el rol no es válido. + * @throws Exception si no existen usuarios con el rol proporcionado o si el rol no es válido. * @throws ValidationException si el rol proporcionado no es válido según las reglas de validación. - * * @see UserDTO * @see ValidationException */ @@ -328,12 +324,11 @@ public List getAllRoles() throws Exception { * valores proporcionados en el objeto {@link UserDTO}. Luego, se validan los nuevos valores antes de * guardar el usuario actualizado. Si alguno de los valores es inválido, se lanza una excepción de validación. * - * @param id el identificador del usuario a actualizar. + * @param id el identificador del usuario a actualizar. * @param updatedUser el objeto {@link UserDTO} que contiene los nuevos valores para el usuario. * @return un objeto {@link UserDTO} con la información actualizada del usuario. - * @throws Exception si el usuario no se pudo actualizar debido a algún error general. + * @throws Exception si el usuario no se pudo actualizar debido a algún error general. * @throws ValidationException si alguno de los campos del usuario proporcionado no es válido según las reglas de validación. - * * @see UserDTO * @see ValidationException */ @@ -379,7 +374,6 @@ public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { * @param id el identificador del usuario que se desea eliminar. * @return un mensaje indicando que el usuario ha sido eliminado correctamente. * @throws Exception si el usuario no existe o si ocurre un error durante el proceso de eliminación. - * * @see UserDTO */ @Override @@ -404,7 +398,6 @@ public String DeleteUser(String id) throws Exception { * @param username el correo electrónico del usuario que se desea cargar. * @return un objeto {@link UserDetails} que contiene la información del usuario cargado. * @throws UsernameNotFoundException si no se encuentra un usuario con el correo electrónico proporcionado. - * * @see UserDetails * @see UsernameNotFoundException */ diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java index 9299b9c..a316576 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java @@ -87,7 +87,7 @@ public interface SUserI extends UserDetailsService { /** * Actualiza los datos de un usuario. * - * @param id el identificador único del usuario a actualizar. + * @param id el identificador único del usuario a actualizar. * @param updatedUser un objeto {@link UserDTO} con los datos actualizados del usuario. * @return el objeto {@link UserDTO} con los datos del usuario actualizado. * @throws Exception si no se encuentra el usuario o si ocurre un error durante la actualización. diff --git a/src/main/java/smartpot/com/api/Users/Validator/VUser.java b/src/main/java/smartpot/com/api/Users/Validator/VUser.java index 6b27ab7..64aa175 100644 --- a/src/main/java/smartpot/com/api/Users/Validator/VUser.java +++ b/src/main/java/smartpot/com/api/Users/Validator/VUser.java @@ -25,10 +25,14 @@ @Component public class VUser implements VUserI { - /** Indica si la validación fue exitosa. */ + /** + * Indica si la validación fue exitosa. + */ public boolean valid; - /** Lista de errores de validación. */ + /** + * Lista de errores de validación. + */ public List errors; /** From 4ba166e657418acb68892f8b7c817c5fc069313d Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Sat, 11 Jan 2025 08:38:13 -0500 Subject: [PATCH 17/25] Refactor .env.example with placeholder variables --- .env.example | 88 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/.env.example b/.env.example index fe5afe2..96ca01c 100644 --- a/.env.example +++ b/.env.example @@ -1,47 +1,63 @@ -# Configuración APP -APP_NAME=app -PORT=port -TITLE=title -DESCRIPTION=description -VERSION=version -AUTHOR=author +# Application Configuration +# Basic settings for your application +APP_NAME= # Name of your application +PORT= # Port the application will listen on +TITLE= # Title of the API +DESCRIPTION=<DESCRIPTION> # Description of the API +VERSION=<VERSION> # Version of your API +AUTHOR=<AUTHOR> # Author(s) of the API # MongoDB Credentials -DATA_CONNECTION_METHOD=method # Methods (mongodb+srv, mongodb) -DATA_SOURCE_USERNAME=username -DATA_SOURCE_PASSWORD=password -DATA_SOURCE_DOMAIN=domain -DATA_SOURCE_DB=database -DATA_PARAMS=params +# Settings for connecting to MongoDB +DATA_CONNECTION_METHOD=<CONNECTION_METHOD> # Connection method (mongodb+srv or mongodb) +DATA_SOURCE_USERNAME=<USERNAME> # MongoDB username +DATA_SOURCE_PASSWORD=<PASSWORD> # MongoDB password +DATA_SOURCE_DOMAIN=<DOMAIN> # MongoDB server domain +DATA_SOURCE_DB=<DATABASE> # Database name +DATA_PARAMS=<CONNECTION_PARAMS> # Additional connection parameters # Redis Credentials -CACHE_TYPE=redis -CACHE_HOST=host -CACHE_PORT=port -CACHE_DB=id -CACHE_USERNAME=user -CACHE_PASSWORD=password -CACHE_TIMEOUT=ms -CACHE_MAXIMUMACTIVECONNECTIONCOUNT=max +# Settings for connecting to Redis (used for caching) +CACHE_TYPE=<CACHE_TYPE> # Cache type (redis) +CACHE_HOST=<REDIS_HOST> # Redis host +CACHE_PORT=<REDIS_PORT> # Redis port +CACHE_DB=<REDIS_DB> # Redis database ID (usually 0) +CACHE_USERNAME=<REDIS_USERNAME> # Redis username +CACHE_PASSWORD=<REDIS_PASSWORD> # Redis password +CACHE_TIMEOUT=<CACHE_TIMEOUT> # Timeout for cache operations (in ms) +CACHE_LETTUCE_POOL_MAX_ACTIVE=<MAX_ACTIVE> # Max active connections in Redis pool +CACHE_LETTUCE_POOL_MAX_WAIT=<MAX_WAIT> # Max wait time for Redis connections (in ms) +CACHE_LETTUCE_POOL_MAX_IDLE=<MAX_IDLE> # Max idle connections in Redis pool +CACHE_LETTUCE_POOL_MIN_IDLE=<MIN_IDLE> # Min idle connections in Redis pool + +# Cache Configuration +# Settings for cache behavior +CACHE_TIME_TO_LIVE=<CACHE_TTL> # Time to live for cache items (in ms) +CACHE_NULL_VALUES=<BOOLEAN> # Whether to store null values in cache (true/false) # Email Credentials -MAIL_HOST=SMTP.GMAIL.COM -MAIL_PORT=587 -MAIL_USERNAME=<LOGIN USER TO SMTP SERVER> -MAIL_PASSWORD=<LOGIN PASSWORD TO SMTP SERVER> -MAIL_PROPERTIES_SMTP_AUTH=TRUE -MAIL_PROPERTIES_SMTP_STARTTLS_ENABLE=TRUE +# Settings for sending emails via SMTP +MAIL_HOST=<SMTP_HOST> # SMTP server (e.g., Gmail) +MAIL_PORT=<SMTP_PORT> # SMTP port (587 for TLS) +MAIL_USERNAME=<SMTP_USERNAME> # SMTP login username +MAIL_PASSWORD=<SMTP_PASSWORD> # SMTP login password +MAIL_PROPERTIES_SMTP_AUTH=<TRUE/FALSE> # Enable SMTP authentication (true/false) +MAIL_PROPERTIES_SMTP_STARTTLS_ENABLE=<TRUE/FALSE> # Enable STARTTLS for secure connection # JWT Credentials -SECURITY_JWT_SECRET_KEY=secret-key -SECURITY_JWT_EXPIRATION=jwt-expiration -SECURITY_PUBLIC_ROUTES=/** +# Settings for JWT (JSON Web Token) authentication +SECURITY_JWT_SECRET_KEY=<JWT_SECRET_KEY> # Secret key for signing JWT tokens +SECURITY_JWT_EXPIRATION=<JWT_EXPIRATION> # JWT expiration time (in ms) +SECURITY_PUBLIC_ROUTES=<PUBLIC_ROUTES> # Public routes that do not require authentication (e.g., /auth/login) -# Https Headers -HEADER_CORS_ALLOWED_ORIGINS=* +# HTTPS Headers (CORS) +# Settings for Cross-Origin Resource Sharing (CORS) +HEADER_CORS_ALLOWED_ORIGINS=<ALLOWED_ORIGINS> # Allowed origins for CORS (e.g., http://localhost:3000) -# Config TOMCAT -SERVER_TOMCAT_TIMEOUT=ms +# Tomcat Configuration +# Settings for your Tomcat server +SERVER_TOMCAT_TIMEOUT=<TOMCAT_TIMEOUT> # Timeout for Tomcat server (in ms) -# Logs (INFO, DEBUG, OFF) -DEBUGGER_MODE=mode \ No newline at end of file +# Log Level Configuration +# Define the logging level (e.g., INFO, DEBUG, OFF) +DEBUGGER_MODE=<DEBUG_MODE> # Log level: INFO, DEBUG, or OFF From f7251fb79430ae5040cd6c0a22f2ce1dbdffbe47 Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Sat, 11 Jan 2025 12:50:56 -0500 Subject: [PATCH 18/25] Refactoring functions for the mail delivery service --- .../java/smartpot/com/api/Mail/Controller/EmailController.java | 2 +- .../smartpot/com/api/Mail/Model/DAO/Service/EmailService.java | 2 +- .../smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java b/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java index 3618a9c..84c800a 100644 --- a/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java +++ b/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java @@ -67,7 +67,7 @@ public EmailController(EmailServiceI emailServiceI) { }) public ResponseEntity<?> getAllCrops() { try { - return new ResponseEntity<>(emailServiceI.getAllEmails(), HttpStatus.OK); + return new ResponseEntity<>(emailServiceI.getAllMails(), HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(new ErrorResponse(e.getMessage(), HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); } diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java index ee52f61..5d5a1ae 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java @@ -89,7 +89,7 @@ public void sendMailWithAttachment(EmailDetails details) { * @see EmailMapper */ @Override - public List<EmailDTO> getAllEmails() throws Exception { + public List<EmailDTO> getAllMails() throws Exception { return Optional.of(emailRepository.findAll()) .filter(emails -> !emails.isEmpty()) .map(emails -> emails.stream() diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java index 6659c63..41f9c39 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java @@ -11,5 +11,5 @@ public interface EmailServiceI { void sendMailWithAttachment(EmailDetails details); - List<EmailDTO> getAllEmails() throws Exception; + List<EmailDTO> getAllMails() throws Exception; } From 8339c64d03f80d08fc1007114aa9db2cfe13430a Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Sat, 11 Jan 2025 12:51:28 -0500 Subject: [PATCH 19/25] Test Separation of exceptions for handling different http status codes --- .../com/api/Users/Controller/UserController.java | 14 ++++++++++---- .../com/api/Users/Model/DAO/Service/SUser.java | 4 ++-- .../com/api/Users/Model/DAO/Service/SUserI.java | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java index 4c8b5ee..56612cd 100644 --- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java +++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.ValidationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; import org.springframework.http.HttpStatus; @@ -78,8 +79,11 @@ public UserController(SUserI serviceUser) { responseCode = "201", content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDTO.class))), - @ApiResponse(responseCode = "404", - description = "No se pudo crear el usuario.", + @ApiResponse(responseCode = "403", + description = "No se pudo crear el usuario, error de validación", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode = "409", + description = "Conflicto al crear el usuario.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) public ResponseEntity<?> createUser( @@ -87,8 +91,10 @@ public ResponseEntity<?> createUser( required = true) @RequestBody UserDTO userDTO) { try { return new ResponseEntity<>(serviceUser.CreateUser(userDTO), HttpStatus.CREATED); - } catch (Exception e) { - return new ResponseEntity<>(new ErrorResponse("Error al crear el usuario [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); + } catch (ValidationException e) { + return new ResponseEntity<>(new ErrorResponse("Error al crear el usuario [" + e.getMessage() + "]", HttpStatus.FORBIDDEN.value()), HttpStatus.FORBIDDEN); + } catch (IllegalStateException e) { + return new ResponseEntity<>(new ErrorResponse("Error al crear el usuario [" + e.getMessage() + "]", HttpStatus.CONFLICT.value()), HttpStatus.CONFLICT); } } diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index 70cd679..b036bcd 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -72,7 +72,7 @@ public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { */ @Override @CachePut(value = "users", key = "#userDTO.id") - public UserDTO CreateUser(UserDTO userDTO) throws Exception { + public UserDTO CreateUser(UserDTO userDTO) throws ValidationException, IllegalStateException { return Optional.of(userDTO) .filter(dto -> !repositoryUser.existsByEmail(dto.getEmail())) .map(ValidDTO -> { @@ -95,7 +95,7 @@ public UserDTO CreateUser(UserDTO userDTO) throws Exception { .map(mapperUser::toEntity) .map(repositoryUser::save) .map(mapperUser::toDTO) - .orElseThrow(() -> new Exception("El Usuario ya existe")); + .orElseThrow(() -> new IllegalStateException("El Usuario ya existe")); } diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java index a316576..b08215e 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java @@ -1,5 +1,6 @@ package smartpot.com.api.Users.Model.DAO.Service; +import jakarta.validation.ValidationException; import org.springframework.security.core.userdetails.UserDetailsService; import smartpot.com.api.Users.Model.DTO.UserDTO; @@ -21,7 +22,7 @@ public interface SUserI extends UserDetailsService { * @return el objeto {@link UserDTO} del usuario creado. * @throws Exception si el usuario ya existe o si ocurre un error durante la creación. */ - UserDTO CreateUser(UserDTO userDTO) throws Exception; + UserDTO CreateUser(UserDTO userDTO) throws ValidationException, IllegalStateException; /** * Obtiene todos los usuarios registrados en el sistema. From dd668feee2a83c4edb6dd516eb303bc483f4950c Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Sat, 11 Jan 2025 22:25:54 -0500 Subject: [PATCH 20/25] Configuring annotations for caching --- .../api/Crops/Model/DAO/Service/SCrop.java | 23 +++++++++++++-- .../com/api/Crops/Model/DTO/CropDTO.java | 4 ++- .../Mail/Model/DAO/Service/EmailService.java | 2 ++ .../api/Users/Model/DAO/Service/SUser.java | 28 +++++++++++-------- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index c9467c1..412741c 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -6,6 +6,9 @@ import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import smartpot.com.api.Crops.Mapper.MCrop; import smartpot.com.api.Crops.Model.DAO.Repository.RCrop; @@ -31,6 +34,7 @@ @Service public class SCrop implements SCropI { + private final SCrop serviceCrop; private final RCrop repositoryCrop; private final SUserI serviceUser; private final MCrop mapperCrop; @@ -53,7 +57,8 @@ public class SCrop implements SCropI { * @see VCropI */ @Autowired - public SCrop(RCrop repositoryCrop, SUserI serviceUser, MCrop mapperCrop, VCropI validatorCrop) { + public SCrop(SCrop serviceCrop, RCrop repositoryCrop, SUserI serviceUser, MCrop mapperCrop, VCropI validatorCrop) { + this.serviceCrop = serviceCrop; this.repositoryCrop = repositoryCrop; this.serviceUser = serviceUser; this.mapperCrop = mapperCrop; @@ -81,6 +86,7 @@ public SCrop(RCrop repositoryCrop, SUserI serviceUser, MCrop mapperCrop, VCropI * @see MCrop */ @Override + @CachePut(value = "crops", key = "#cropDTO.id") public CropDTO createCrop(CropDTO cropDTO) throws Exception { return Optional.of(cropDTO) .map(ValidCropDTO -> { @@ -121,6 +127,7 @@ public CropDTO createCrop(CropDTO cropDTO) throws Exception { * @see MCrop */ @Override + @Cacheable(value = "crops", key = "'all_crops'") public List<CropDTO> getAllCrops() throws Exception { return Optional.of(repositoryCrop.findAll()) .filter(crops -> !crops.isEmpty()) @@ -149,6 +156,7 @@ public List<CropDTO> getAllCrops() throws Exception { * @see MCrop */ @Override + @Cacheable(value = "crops", key = "'id_'+#id") public CropDTO getCropById(String id) throws Exception { return Optional.of(id) .map(ValidCropId -> { @@ -184,6 +192,7 @@ public CropDTO getCropById(String id) throws Exception { * @see MCrop */ @Override + @Cacheable(value = "crops", key = "'user_'+#id") public List<CropDTO> getCropsByUser(String id) throws Exception { return Optional.of(serviceUser.getUserById(id)) .map(validUser -> new ObjectId(validUser.getId())) @@ -208,6 +217,7 @@ public List<CropDTO> getCropsByUser(String id) throws Exception { * @see #getCropsByUser(String) */ @Override + @Cacheable(value = "crops", key = "'count_user_'+#id") public long countCropsByUser(String id) throws Exception { return getCropsByUser(id).size(); } @@ -229,8 +239,10 @@ public long countCropsByUser(String id) throws Exception { * @see VCropI * @see RCrop * @see MCrop + * @see SCrop */ @Override + @Cacheable(value = "crops", key = "'type_'+#type") public List<CropDTO> getCropsByType(String type) throws Exception { return Optional.of(type) .map(ValidType -> { @@ -261,6 +273,7 @@ public List<CropDTO> getCropsByType(String type) throws Exception { * @see Type */ @Override + @Cacheable(value = "crops", key = "'all_types'") public List<String> getAllTypes() throws Exception { return Optional.of(Type.getTypeNames()) .filter(types -> !types.isEmpty()) @@ -287,6 +300,7 @@ public List<String> getAllTypes() throws Exception { * @see MCrop */ @Override + @Cacheable(value = "crops", key = "'status_'+#status") public List<CropDTO> getCropsByStatus(String status) throws Exception { return Optional.of(status) .map(ValidStatus -> { @@ -317,6 +331,7 @@ public List<CropDTO> getCropsByStatus(String status) throws Exception { * @see Status */ @Override + @Cacheable(value = "crops", key = "'all_status'") public List<String> getAllStatus() throws Exception { return Optional.of(Status.getStatusNames()) .filter(status -> !status.isEmpty()) @@ -347,8 +362,9 @@ public List<String> getAllStatus() throws Exception { * @see MCrop */ @Override + @CachePut(value = "crops", key = "'id_'+#id") public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { - CropDTO existingCrop = getCropById(id); + CropDTO existingCrop = serviceCrop.getCropById(id); return Optional.of(updateCrop) .map(dto -> { existingCrop.setType(dto.getType() != null ? dto.getType() : existingCrop.getType()); @@ -392,8 +408,9 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { * @see RCrop */ @Override + @CacheEvict(value = "crops", key = "'id_'+#id") public String deleteCrop(String id) throws Exception { - return Optional.of(getCropById(id)) + return Optional.of(serviceCrop.getCropById(id)) .map(user -> { repositoryCrop.deleteById(new ObjectId(user.getId())); return "El Cultivo con ID '" + id + "' fue eliminado."; diff --git a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java index 38adc8d..f7cecd9 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DTO/CropDTO.java @@ -5,6 +5,8 @@ import lombok.Data; import lombok.RequiredArgsConstructor; +import java.io.Serializable; + /** * DTO para representar los datos de un cultivo. @@ -15,7 +17,7 @@ @Data @AllArgsConstructor @RequiredArgsConstructor -public class CropDTO { +public class CropDTO implements Serializable { @Schema(description = "ID único del cultivo, generado automáticamente por la base de datos.", example = "60b63b8f3e111f8d44d45e72", hidden = true) diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java index 5d5a1ae..90c9fdd 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java +++ b/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; @@ -89,6 +90,7 @@ public void sendMailWithAttachment(EmailDetails details) { * @see EmailMapper */ @Override + @Cacheable(value = "mails", key = "'all_mails'") public List<EmailDTO> getAllMails() throws Exception { return Optional.of(emailRepository.findAll()) .filter(emails -> !emails.isEmpty()) diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index b036bcd..c0768cd 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -34,6 +34,8 @@ @Builder @Service public class SUser implements SUserI { + + private final SUser serviceUser; private final RUser repositoryUser; private final MUser mapperUser; private final VUserI validatorUser; @@ -46,7 +48,8 @@ public class SUser implements SUserI { * @param validatorUser validador que valida los datos de usuario. */ @Autowired - public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { + public SUser(SUser serviceUser,RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { + this.serviceUser = serviceUser; this.repositoryUser = repositoryUser; this.mapperUser = mapperUser; this.validatorUser = validatorUser; @@ -64,9 +67,10 @@ public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { * * @param userDTO el objeto {@link UserDTO} que contiene los datos del nuevo usuario. * @return un objeto {@link UserDTO} que representa al usuario creado. - * @throws Exception si el usuario ya existe en la base de datos (por email) o si hay un error de validación. + * @throws IllegalStateException si el usuario ya existe en la base de datos (por email). + * @throws ValidationException si el usuario tiene un error de validación. * @throws ValidationException si las validaciones de los campos del usuario no son exitosas. - * * + * * @see UserDTO * @see ValidationException */ @@ -137,7 +141,7 @@ public List<UserDTO> getAllUsers() throws Exception { * @see ValidationException */ @Override - @Cacheable(value = "users", key = "#id") + @Cacheable(value = "users", key = "'id_'+#id") public UserDTO getUserById(String id) throws Exception { return Optional.of(id) .map(ValidId -> { @@ -172,7 +176,7 @@ public UserDTO getUserById(String id) throws Exception { * @see ValidationException */ @Override - @Cacheable(value = "users", key = "#email") + @Cacheable(value = "users", key = "'email_'+#email") public UserDTO getUserByEmail(String email) throws Exception { return Optional.of(email) .map(ValidEmail -> { @@ -207,7 +211,7 @@ public UserDTO getUserByEmail(String email) throws Exception { * @see ValidationException */ @Override - @Cacheable(value = "users", key = "#name") + @Cacheable(value = "users", key = "'name_'+#name") public List<UserDTO> getUsersByName(String name) throws Exception { return Optional.of(name) .map(ValidName -> { @@ -243,7 +247,7 @@ public List<UserDTO> getUsersByName(String name) throws Exception { * @see ValidationException */ @Override - @Cacheable(value = "users", key = "#lastname") + @Cacheable(value = "users", key = "'lastname_'+#lastname") public List<UserDTO> getUsersByLastname(String lastname) throws Exception { return Optional.of(lastname) .map(ValidLastname -> { @@ -279,7 +283,7 @@ public List<UserDTO> getUsersByLastname(String lastname) throws Exception { * @see ValidationException */ @Override - @Cacheable(value = "users", key = "#role") + @Cacheable(value = "users", key = "'role:'+#role") public List<UserDTO> getUsersByRole(String role) throws Exception { return Optional.of(role) .map(ValidRole -> { @@ -333,9 +337,9 @@ public List<String> getAllRoles() throws Exception { * @see ValidationException */ @Override - @CachePut(value = "users", key = "#id") + @CachePut(value = "users", key = "'id:'+#id") public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { - UserDTO existingUser = getUserById(id); + UserDTO existingUser = serviceUser.getUserById(id); return Optional.of(updatedUser) .map(dto -> { existingUser.setName(dto.getName() != null ? dto.getName() : existingUser.getName()); @@ -377,9 +381,9 @@ public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { * @see UserDTO */ @Override - @CacheEvict(cacheNames = "users", allEntries = true) + @CacheEvict(value = "users", key = "'id_'+#id") public String DeleteUser(String id) throws Exception { - return Optional.of(getUserById(id)) + return Optional.of(serviceUser.getUserById(id)) .map(user -> { repositoryUser.deleteById(new ObjectId(user.getId())); return "El Usuario con ID '" + id + "' fue eliminado."; From bae758adb28986033617a2740764a3eb85307327 Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Sun, 12 Jan 2025 14:53:51 -0500 Subject: [PATCH 21/25] Refactor: improve enums, update controllers, and add Swagger documentation --- .../Controller/CommandController.java | 90 +++++++++++++------ .../Commands/Model/DAO/Service/SCommand.java | 24 +++-- .../Commands/Model/DAO/Service/SCommandI.java | 3 +- .../api/Commands/Model/DTO/CommandDTO.java | 4 +- .../api/Crops/Controller/CropController.java | 8 +- .../api/Crops/Model/DAO/Service/SCrop.java | 29 +++--- .../com/api/Crops/Model/Entity/Crop.java | 4 +- .../api/Crops/Model/Entity/CropStatus.java | 6 ++ .../com/api/Crops/Model/Entity/CropType.java | 5 ++ .../com/api/Crops/Model/Entity/Status.java | 21 ----- .../com/api/Crops/Model/Entity/Type.java | 15 ---- .../com/api/Crops/Validator/VCrop.java | 13 +-- .../api/Users/Model/DAO/Service/SUser.java | 19 ++-- .../com/api/Users/Model/Entity/Role.java | 20 ----- .../com/api/Users/Model/Entity/User.java | 2 +- .../com/api/Users/Model/Entity/UserRole.java | 5 ++ .../com/api/Users/Validator/VUser.java | 8 +- 17 files changed, 154 insertions(+), 122 deletions(-) create mode 100644 src/main/java/smartpot/com/api/Crops/Model/Entity/CropStatus.java create mode 100644 src/main/java/smartpot/com/api/Crops/Model/Entity/CropType.java delete mode 100644 src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java delete mode 100644 src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java delete mode 100644 src/main/java/smartpot/com/api/Users/Model/Entity/Role.java create mode 100644 src/main/java/smartpot/com/api/Users/Model/Entity/UserRole.java diff --git a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java index b82bd1c..faee433 100644 --- a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java +++ b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java @@ -1,54 +1,94 @@ package smartpot.com.api.Commands.Controller; -import org.bson.types.ObjectId; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import smartpot.com.api.Commands.Model.DAO.Service.SCommandI; +import smartpot.com.api.Commands.Model.DTO.CommandDTO; import smartpot.com.api.Commands.Model.Entity.Command; -import smartpot.com.api.Crops.Model.DAO.Service.SCropI; import smartpot.com.api.Crops.Model.DTO.CropDTO; +import smartpot.com.api.Responses.ErrorResponse; import java.util.Date; -import java.util.List; -import java.util.Optional; @RestController @RequestMapping("/Comandos") public class CommandController { private final SCommandI serviceCommand; - private final SCropI serviceCrop; @Autowired - public CommandController(SCommandI serviceCommand, SCropI serviceCrop) { + public CommandController(SCommandI serviceCommand) { this.serviceCommand = serviceCommand; - this.serviceCrop = serviceCrop; } - @GetMapping("/All") - public List<Command> getAllCommand() { - return serviceCommand.getAllCommands(); + @PostMapping("/Create") + @Operation(summary = "Crear un nuevo comando", + description = "Crea un nuevo comando utilizando los datos proporcionados en el objeto CommandDTO. " + + "Si la creación es exitosa, se devuelve el comando recién creado.", + responses = { + @ApiResponse(description = "Comando creado", + responseCode = "201", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = CropDTO.class))), + @ApiResponse(responseCode = "404", + description = "No se pudo crear el Comando debido a un error.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity<?> createCommand(@Parameter(description = "Datos del nuevo comando que se va a crear. Debe incluir tipo y cultivo asociado.", + required = true) @RequestBody CommandDTO commandDTO) { + try { + return new ResponseEntity<>(serviceCommand.createCommand(commandDTO), HttpStatus.CREATED); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al crear el comando [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); + + } } - @GetMapping("/id/{id}") - public Command getUserById(@PathVariable String id) { - return serviceCommand.getCommandById(id); + @GetMapping("/All") + @Operation(summary = "Obtener todos los comandos", + description = "Recupera todos los comandos registrados en el sistema. " + + "En caso de no haber comandos, se devolverá una excepción.", + responses = { + @ApiResponse(description = "Comandos encontrados", + responseCode = "200", + content = @Content(mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = CommandDTO.class)))), + @ApiResponse(responseCode = "404", + description = "No se encontraron Comandos registrados.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity<?> getAllCommand() { + try { + return new ResponseEntity<>(serviceCommand.getAllCommands(), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al obtener los comandos [" + e.getMessage() + "]", HttpStatus.INTERNAL_SERVER_ERROR.value()), HttpStatus.INTERNAL_SERVER_ERROR); + } } - @PostMapping("/commandCreate/{cropId}") - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity<Command> createCommand(@PathVariable String cropId, @RequestBody Command newCommand) throws Exception { - Optional<CropDTO> cropOpt = Optional.ofNullable(serviceCrop.getCropById(cropId)); - if (cropOpt.isPresent()) { - newCommand.setCrop(new ObjectId(cropId)); - newCommand.setDateCreated(new Date()); - newCommand.setStatus("PENDING"); - Command savedCommand = serviceCommand.createCommand(newCommand); - return ResponseEntity.ok(savedCommand); - } else { - return ResponseEntity.notFound().build(); + @GetMapping("/id/{id}") + @Operation(summary = "Buscar comando por ID", + description = "Recupera un comando utilizando su ID único. " + + "Si el comando no existe, se devolverá un error con el código HTTP 404.", + responses = { + @ApiResponse(description = "Comando encontrado", + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = CommandDTO.class))), + @ApiResponse(responseCode = "404", + description = "Comando no encontrado con el ID especificado.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity<?> getUserById(@PathVariable String id) { + try { + return new ResponseEntity<>(serviceCommand.getCommandById(id), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al buscar el comando con ID '" + id + "' [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); } } diff --git a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java index f89aa41..8c2ed73 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java +++ b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java @@ -6,14 +6,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import smartpot.com.api.Commands.Mapper.MCommand; import smartpot.com.api.Commands.Model.DAO.Repository.RCommand; +import smartpot.com.api.Commands.Model.DTO.CommandDTO; import smartpot.com.api.Commands.Model.Entity.Command; import smartpot.com.api.Crops.Model.DAO.Service.SCropI; import smartpot.com.api.Exception.ApiException; import smartpot.com.api.Exception.ApiResponse; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.Optional; @Data @Builder @@ -22,11 +26,13 @@ public class SCommand implements SCommandI { private final RCommand repositoryCommand; private final SCropI serviceCrop; + private final MCommand mapperCommand; @Autowired - public SCommand(RCommand repositoryCommand, SCropI serviceCrop) { + public SCommand(RCommand repositoryCommand, SCropI serviceCrop, MCommand mapperCommand) { this.repositoryCommand = repositoryCommand; this.serviceCrop = serviceCrop; + this.mapperCommand = mapperCommand; } @Override @@ -40,10 +46,18 @@ public Command getCommandById(String id) { } @Override - public Command createCommand(Command newCommand) { - newCommand.setDateCreated(new Date()); - newCommand.setStatus("PENDING"); - return repositoryCommand.save(newCommand); + public CommandDTO createCommand(CommandDTO commandDTO) throws IllegalStateException { + return Optional.of(commandDTO) + .map(dto -> { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dto.setDateCreated(formatter.format(new Date())); + dto.setStatus("PENDING"); + return dto; + }) + .map(mapperCommand::toEntity) + .map(repositoryCommand::save) + .map(mapperCommand::toDTO) + .orElseThrow(() -> new IllegalStateException("El Comando ya existe")); } @Override diff --git a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java index 2f74d19..d12777d 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java +++ b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java @@ -1,5 +1,6 @@ package smartpot.com.api.Commands.Model.DAO.Service; +import smartpot.com.api.Commands.Model.DTO.CommandDTO; import smartpot.com.api.Commands.Model.Entity.Command; import java.util.List; @@ -9,7 +10,7 @@ public interface SCommandI { Command getCommandById(String id); - Command createCommand(Command newCommand); + CommandDTO createCommand(CommandDTO newCommand); Command updateCommand(String id, Command upCommand) throws Exception; diff --git a/src/main/java/smartpot/com/api/Commands/Model/DTO/CommandDTO.java b/src/main/java/smartpot/com/api/Commands/Model/DTO/CommandDTO.java index 4c53bae..929c382 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DTO/CommandDTO.java +++ b/src/main/java/smartpot/com/api/Commands/Model/DTO/CommandDTO.java @@ -2,8 +2,10 @@ import lombok.Data; +import java.io.Serializable; + @Data -public class CommandDTO { +public class CommandDTO implements Serializable { private String id; private String commandType; private String status; diff --git a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java index 7f27495..5c18eee 100644 --- a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java +++ b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java @@ -43,7 +43,7 @@ public CropController(SCropI serviceCrop) { * Si el cultivo es creado exitosamente, se devolverá el objeto con la información del cultivo recién creado.</p> * <p>En caso de que ocurra un error durante la creación del cultivo, se devolverá un mensaje de error con el código HTTP 404.</p> * - * @param newCropDto El objeto {@link CropDTO} que contiene los datos del nuevo cultivo a crear. Este objeto debe incluir toda la información necesaria para crear el cultivo. + * @param cropDTO El objeto {@link CropDTO} que contiene los datos del nuevo cultivo a crear. Este objeto debe incluir toda la información necesaria para crear el cultivo. * @return Un objeto {@link ResponseEntity} que contiene: * <ul> * <li>El cultivo recién creado (código HTTP 201).</li> @@ -69,9 +69,9 @@ public CropController(SCropI serviceCrop) { content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) public ResponseEntity<?> createCrop(@Parameter(description = "Datos del nuevo cultivo que se va a crear. Debe incluir tipo y usuario asociado.", - required = true) @RequestBody CropDTO newCropDto) { + required = true) @RequestBody CropDTO cropDTO) { try { - return new ResponseEntity<>(serviceCrop.createCrop(newCropDto), HttpStatus.CREATED); + return new ResponseEntity<>(serviceCrop.createCrop(cropDTO), HttpStatus.CREATED); } catch (Exception e) { return new ResponseEntity<>(new ErrorResponse("Error al crear el cultivo [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); @@ -99,7 +99,7 @@ public ResponseEntity<?> createCrop(@Parameter(description = "Datos del nuevo cu description = "Recupera todos los cultivos registrados en el sistema. " + "En caso de no haber cultivos, se devolverá una excepción.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(description = "Cultivos encontrados", + @ApiResponse(description = "Cultivos encontrados", responseCode = "200", content = @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = UserDTO.class)))), diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java index 412741c..e35429b 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java @@ -13,11 +13,12 @@ import smartpot.com.api.Crops.Mapper.MCrop; import smartpot.com.api.Crops.Model.DAO.Repository.RCrop; import smartpot.com.api.Crops.Model.DTO.CropDTO; -import smartpot.com.api.Crops.Model.Entity.Status; -import smartpot.com.api.Crops.Model.Entity.Type; +import smartpot.com.api.Crops.Model.Entity.CropStatus; +import smartpot.com.api.Crops.Model.Entity.CropType; import smartpot.com.api.Crops.Validator.VCropI; import smartpot.com.api.Users.Model.DAO.Service.SUserI; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -34,7 +35,6 @@ @Service public class SCrop implements SCropI { - private final SCrop serviceCrop; private final RCrop repositoryCrop; private final SUserI serviceUser; private final MCrop mapperCrop; @@ -57,8 +57,7 @@ public class SCrop implements SCropI { * @see VCropI */ @Autowired - public SCrop(SCrop serviceCrop, RCrop repositoryCrop, SUserI serviceUser, MCrop mapperCrop, VCropI validatorCrop) { - this.serviceCrop = serviceCrop; + public SCrop(RCrop repositoryCrop, SUserI serviceUser, MCrop mapperCrop, VCropI validatorCrop) { this.repositoryCrop = repositoryCrop; this.serviceUser = serviceUser; this.mapperCrop = mapperCrop; @@ -270,12 +269,16 @@ public List<CropDTO> getCropsByType(String type) throws Exception { * @return Una lista de cadenas {@link String} que representan los nombres de los tipos de cultivo encontrados. * @throws Exception Si ocurre un error al obtener los tipos de cultivo o si no se encuentran tipos registrados. * @see String - * @see Type + * @see CropType */ @Override @Cacheable(value = "crops", key = "'all_types'") public List<String> getAllTypes() throws Exception { - return Optional.of(Type.getTypeNames()) + return Optional.of( + Arrays.stream(CropType.values()) + .map(Enum::name) + .collect(Collectors.toList()) + ) .filter(types -> !types.isEmpty()) .orElseThrow(() -> new Exception("No existe ningún tipo de cultivo")); } @@ -328,12 +331,16 @@ public List<CropDTO> getCropsByStatus(String status) throws Exception { * @return Una lista de cadenas {@link String} que representan los estados de cultivo encontrados. * @throws Exception Si ocurre un error al buscar los estados de cultivo o si no se encuentran estados registrados. * @see String - * @see Status + * @see CropStatus */ @Override @Cacheable(value = "crops", key = "'all_status'") public List<String> getAllStatus() throws Exception { - return Optional.of(Status.getStatusNames()) + return Optional.of( + Arrays.stream(CropStatus.values()) + .map(Enum::name) + .collect(Collectors.toList()) + ) .filter(status -> !status.isEmpty()) .orElseThrow(() -> new Exception("No existe ningún estados para los cultivos")); } @@ -364,7 +371,7 @@ public List<String> getAllStatus() throws Exception { @Override @CachePut(value = "crops", key = "'id_'+#id") public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { - CropDTO existingCrop = serviceCrop.getCropById(id); + CropDTO existingCrop = getCropById(id); return Optional.of(updateCrop) .map(dto -> { existingCrop.setType(dto.getType() != null ? dto.getType() : existingCrop.getType()); @@ -410,7 +417,7 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { @Override @CacheEvict(value = "crops", key = "'id_'+#id") public String deleteCrop(String id) throws Exception { - return Optional.of(serviceCrop.getCropById(id)) + return Optional.of(getCropById(id)) .map(user -> { repositoryCrop.deleteById(new ObjectId(user.getId())); return "El Cultivo con ID '" + id + "' fue eliminado."; diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java index a0d4c97..623a424 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/Crop.java @@ -34,11 +34,11 @@ public class Crop implements Serializable { private ObjectId id; @Field("status") - private Status status; + private CropStatus cropStatus; @NotEmpty(message = "El tipo no puede estar vacío") @Field("type") - private Type type; + private CropType cropType; /** * ! No se puede hacer referencia a los objetos, dado que obliga a usar la entidad completa, no solo el ObjectId. diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/CropStatus.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/CropStatus.java new file mode 100644 index 0000000..51f9f53 --- /dev/null +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/CropStatus.java @@ -0,0 +1,6 @@ +package smartpot.com.api.Crops.Model.Entity; + +public enum CropStatus { + Dead, Extreme_decomposition, Severe_deterioration, Moderate_deterioration, Healthy_state, intermittent, + Moderate_health, Good_health, Very_healthy, Excellent, Perfect_plant, Unknown; +} diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/CropType.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/CropType.java new file mode 100644 index 0000000..6cb8fb4 --- /dev/null +++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/CropType.java @@ -0,0 +1,5 @@ +package smartpot.com.api.Crops.Model.Entity; + +public enum CropType { + TOMATO, LETTUCE; +} diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java deleted file mode 100644 index 015a1a8..0000000 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java +++ /dev/null @@ -1,21 +0,0 @@ -package smartpot.com.api.Crops.Model.Entity; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public enum Status { - Dead, Extreme_decomposition, Severe_deterioration, Moderate_deterioration, Healthy_state, intermittent, - Moderate_health, Good_health, Very_healthy, Excellent, Perfect_plant, Unknown; - - /** - * Obtiene la lista de nombres de estados de cultivos definidos en el sistema. - * - * @return Una lista con los nombres de los estados. - */ - public static List<String> getStatusNames() { - return Arrays.stream(Status.values()) - .map(Enum::name) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java deleted file mode 100644 index 510849a..0000000 --- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java +++ /dev/null @@ -1,15 +0,0 @@ -package smartpot.com.api.Crops.Model.Entity; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public enum Type { - TOMATO, LETTUCE; - - public static List<String> getTypeNames() { - return Arrays.stream(Type.values()) - .map(Enum::name) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java b/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java index e399ee2..67e2c31 100644 --- a/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java @@ -2,13 +2,14 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Component; -import smartpot.com.api.Crops.Model.Entity.Status; -import smartpot.com.api.Crops.Model.Entity.Type; +import smartpot.com.api.Crops.Model.Entity.CropStatus; +import smartpot.com.api.Crops.Model.Entity.CropType; import java.util.ArrayList; -import java.util.HashSet; +import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; @Component public class VCrop implements VCropI { @@ -80,7 +81,8 @@ public void validateType(String type) { valid = false; } - Set<String> validTypes = new HashSet<>(Type.getTypeNames()); + Set<String> validTypes = Arrays.stream(CropType.values()) + .map(Enum::name).collect(Collectors.toSet()); if (!validTypes.contains(type)) { errors.add("El Tipo de cultivo debe ser uno de los siguientes: " + String.join(", ", validTypes)); @@ -94,7 +96,8 @@ public void validateStatus(String status) { errors.add("El Estado del cultivo no puede estar vacío"); valid = false; } - Set<String> validStatus = new HashSet<>(Status.getStatusNames()); + Set<String> validStatus = Arrays.stream(CropStatus.values()) + .map(Enum::name).collect(Collectors.toSet()); if (!validStatus.contains(status)) { errors.add("El Estado del cultivo debe ser uno de los siguientes: " + String.join(", ", validStatus)); diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java index c0768cd..3d9f72c 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java @@ -15,10 +15,11 @@ import smartpot.com.api.Users.Mapper.MUser; import smartpot.com.api.Users.Model.DAO.Repository.RUser; import smartpot.com.api.Users.Model.DTO.UserDTO; -import smartpot.com.api.Users.Model.Entity.Role; +import smartpot.com.api.Users.Model.Entity.UserRole; import smartpot.com.api.Users.Validator.VUserI; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Optional; @@ -35,7 +36,6 @@ @Service public class SUser implements SUserI { - private final SUser serviceUser; private final RUser repositoryUser; private final MUser mapperUser; private final VUserI validatorUser; @@ -48,8 +48,7 @@ public class SUser implements SUserI { * @param validatorUser validador que valida los datos de usuario. */ @Autowired - public SUser(SUser serviceUser,RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { - this.serviceUser = serviceUser; + public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { this.repositoryUser = repositoryUser; this.mapperUser = mapperUser; this.validatorUser = validatorUser; @@ -310,12 +309,16 @@ public List<UserDTO> getUsersByRole(String role) throws Exception { * * @return una lista de objetos {@link String} que representan a todos los roles de usuario. * @throws Exception si no se encuentra ningún rol de usuario en la base de datos. - * @see Role + * @see UserRole */ @Override @Cacheable(value = "users", key = "'all_rols'") public List<String> getAllRoles() throws Exception { - return Optional.of(Role.getRoleNames()) + return Optional.of( + Arrays.stream(UserRole.values()) + .map(Enum::name) + .collect(Collectors.toList()) + ) .filter(roles -> !roles.isEmpty()) .orElseThrow(() -> new Exception("No existe ningún rol")); } @@ -339,7 +342,7 @@ public List<String> getAllRoles() throws Exception { @Override @CachePut(value = "users", key = "'id:'+#id") public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { - UserDTO existingUser = serviceUser.getUserById(id); + UserDTO existingUser = getUserById(id); return Optional.of(updatedUser) .map(dto -> { existingUser.setName(dto.getName() != null ? dto.getName() : existingUser.getName()); @@ -383,7 +386,7 @@ public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { @Override @CacheEvict(value = "users", key = "'id_'+#id") public String DeleteUser(String id) throws Exception { - return Optional.of(serviceUser.getUserById(id)) + return Optional.of(getUserById(id)) .map(user -> { repositoryUser.deleteById(new ObjectId(user.getId())); return "El Usuario con ID '" + id + "' fue eliminado."; diff --git a/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java b/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java deleted file mode 100644 index bf10ae2..0000000 --- a/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java +++ /dev/null @@ -1,20 +0,0 @@ -package smartpot.com.api.Users.Model.Entity; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public enum Role { - USER, ADMIN, SYSTEM; - - /** - * Obtiene la lista de nombres de roles definidos en el sistema. - * - * @return Una lista con los nombres de los roles. - */ - public static List<String> getRoleNames() { - return Arrays.stream(Role.values()) - .map(Enum::name) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/smartpot/com/api/Users/Model/Entity/User.java b/src/main/java/smartpot/com/api/Users/Model/Entity/User.java index d4c9f68..6b9281b 100644 --- a/src/main/java/smartpot/com/api/Users/Model/Entity/User.java +++ b/src/main/java/smartpot/com/api/Users/Model/Entity/User.java @@ -74,5 +74,5 @@ public class User implements Serializable { @NotEmpty(message = "El rol no puede estar vacío") @Field("role") - private Role role; + private UserRole userRole; } diff --git a/src/main/java/smartpot/com/api/Users/Model/Entity/UserRole.java b/src/main/java/smartpot/com/api/Users/Model/Entity/UserRole.java new file mode 100644 index 0000000..d8fbfcf --- /dev/null +++ b/src/main/java/smartpot/com/api/Users/Model/Entity/UserRole.java @@ -0,0 +1,5 @@ +package smartpot.com.api.Users.Model.Entity; + +public enum UserRole { + USER, ADMIN, SYSTEM +} diff --git a/src/main/java/smartpot/com/api/Users/Validator/VUser.java b/src/main/java/smartpot/com/api/Users/Validator/VUser.java index 64aa175..4b7c83a 100644 --- a/src/main/java/smartpot/com/api/Users/Validator/VUser.java +++ b/src/main/java/smartpot/com/api/Users/Validator/VUser.java @@ -2,13 +2,14 @@ import org.bson.types.ObjectId; import org.springframework.stereotype.Component; -import smartpot.com.api.Users.Model.Entity.Role; +import smartpot.com.api.Users.Model.Entity.UserRole; import java.util.ArrayList; -import java.util.HashSet; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static smartpot.com.api.Users.Validator.UserRegex.*; @@ -174,7 +175,8 @@ public void validateRole(String role) { return; } - Set<String> validRoles = new HashSet<>(Role.getRoleNames()); + Set<String> validRoles = Arrays.stream(UserRole.values()) + .map(Enum::name).collect(Collectors.toSet()); if (!validRoles.contains(role)) { errors.add("El Rol debe ser uno de los siguientes: " + String.join(", ", validRoles)); From 8410eb8a7ba082cb848fdd00de59023d92f792e2 Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Sun, 12 Jan 2025 17:15:05 -0500 Subject: [PATCH 22/25] Refactor: reorganize folder structure for clarity and scalability --- .../com/api/Commands/Controller/CommandController.java | 2 +- .../api/Commands/{Model/DAO => }/Repository/RCommand.java | 2 +- .../com/api/Commands/{Model/DAO => }/Service/SCommand.java | 6 +++--- .../com/api/Commands/{Model/DAO => }/Service/SCommandI.java | 2 +- .../smartpot/com/api/Crops/Controller/CropController.java | 2 +- .../com/api/Crops/{Model/DAO => }/Repository/RCrop.java | 2 +- .../com/api/Crops/{Model/DAO => }/Service/SCrop.java | 6 +++--- .../com/api/Crops/{Model/DAO => }/Service/SCropI.java | 2 +- .../smartpot/com/api/Mail/Controller/EmailController.java | 2 +- .../Mail/{Model/DAO => }/Repository/EmailRepository.java | 2 +- .../com/api/Mail/{Model/DAO => }/Service/EmailService.java | 4 ++-- .../com/api/Mail/{Model/DAO => }/Service/EmailServiceI.java | 2 +- .../Notifications/Controller/NotificationController.java | 2 +- .../{Model/DAO => }/Repository/RNotification.java | 2 +- .../{Model/DAO => }/Service/SNotification.java | 4 ++-- .../{Model/DAO => }/Service/SNotificationI.java | 2 +- .../com/api/Records/Controller/HistoryController.java | 2 +- .../api/Records/{Model/DAO => }/Repository/RHistory.java | 2 +- .../com/api/Records/{Model/DAO => }/Service/SHistory.java | 6 +++--- .../com/api/Records/{Model/DAO => }/Service/SHistoryI.java | 2 +- .../com/api/Security/{Headers => Config}/CorsConfig.java | 2 +- .../com/api/Security/Config/SecurityConfiguration.java | 5 ++--- .../com/api/Security/{Filter => Service}/JwtAuthFilter.java | 5 ++--- .../java/smartpot/com/api/Security/Service/JwtService.java | 4 ++-- .../com/api/Sessions/Controller/SessionController.java | 2 +- .../api/Sessions/{Model/DAO => }/Repository/RSession.java | 2 +- .../com/api/Sessions/{Model/DAO => }/Service/SSession.java | 6 +++--- .../com/api/Sessions/{Model/DAO => }/Service/SSessionI.java | 2 +- .../smartpot/com/api/Users/Controller/UserController.java | 2 +- .../com/api/Users/{Model/DAO => }/Repository/RUser.java | 2 +- .../com/api/Users/{Model/DAO => }/Service/SUser.java | 4 ++-- .../com/api/Users/{Model/DAO => }/Service/SUserI.java | 2 +- 32 files changed, 46 insertions(+), 48 deletions(-) rename src/main/java/smartpot/com/api/Commands/{Model/DAO => }/Repository/RCommand.java (97%) rename src/main/java/smartpot/com/api/Commands/{Model/DAO => }/Service/SCommand.java (96%) rename src/main/java/smartpot/com/api/Commands/{Model/DAO => }/Service/SCommandI.java (88%) rename src/main/java/smartpot/com/api/Crops/{Model/DAO => }/Repository/RCrop.java (98%) rename src/main/java/smartpot/com/api/Crops/{Model/DAO => }/Service/SCrop.java (99%) rename src/main/java/smartpot/com/api/Crops/{Model/DAO => }/Service/SCropI.java (93%) rename src/main/java/smartpot/com/api/Mail/{Model/DAO => }/Repository/EmailRepository.java (83%) rename src/main/java/smartpot/com/api/Mail/{Model/DAO => }/Service/EmailService.java (97%) rename src/main/java/smartpot/com/api/Mail/{Model/DAO => }/Service/EmailServiceI.java (86%) rename src/main/java/smartpot/com/api/Notifications/{Model/DAO => }/Repository/RNotification.java (95%) rename src/main/java/smartpot/com/api/Notifications/{Model/DAO => }/Service/SNotification.java (96%) rename src/main/java/smartpot/com/api/Notifications/{Model/DAO => }/Service/SNotificationI.java (89%) rename src/main/java/smartpot/com/api/Records/{Model/DAO => }/Repository/RHistory.java (94%) rename src/main/java/smartpot/com/api/Records/{Model/DAO => }/Service/SHistory.java (98%) rename src/main/java/smartpot/com/api/Records/{Model/DAO => }/Service/SHistoryI.java (94%) rename src/main/java/smartpot/com/api/Security/{Headers => Config}/CorsConfig.java (96%) rename src/main/java/smartpot/com/api/Security/{Filter => Service}/JwtAuthFilter.java (92%) rename src/main/java/smartpot/com/api/Sessions/{Model/DAO => }/Repository/RSession.java (94%) rename src/main/java/smartpot/com/api/Sessions/{Model/DAO => }/Service/SSession.java (97%) rename src/main/java/smartpot/com/api/Sessions/{Model/DAO => }/Service/SSessionI.java (90%) rename src/main/java/smartpot/com/api/Users/{Model/DAO => }/Repository/RUser.java (98%) rename src/main/java/smartpot/com/api/Users/{Model/DAO => }/Service/SUser.java (99%) rename src/main/java/smartpot/com/api/Users/{Model/DAO => }/Service/SUserI.java (98%) diff --git a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java index faee433..eedaf9d 100644 --- a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java +++ b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java @@ -10,9 +10,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import smartpot.com.api.Commands.Model.DAO.Service.SCommandI; import smartpot.com.api.Commands.Model.DTO.CommandDTO; import smartpot.com.api.Commands.Model.Entity.Command; +import smartpot.com.api.Commands.Service.SCommandI; import smartpot.com.api.Crops.Model.DTO.CropDTO; import smartpot.com.api.Responses.ErrorResponse; diff --git a/src/main/java/smartpot/com/api/Commands/Model/DAO/Repository/RCommand.java b/src/main/java/smartpot/com/api/Commands/Repository/RCommand.java similarity index 97% rename from src/main/java/smartpot/com/api/Commands/Model/DAO/Repository/RCommand.java rename to src/main/java/smartpot/com/api/Commands/Repository/RCommand.java index ed01b5a..20c84c4 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Repository/RCommand.java +++ b/src/main/java/smartpot/com/api/Commands/Repository/RCommand.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Commands.Model.DAO.Repository; +package smartpot.com.api.Commands.Repository; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java b/src/main/java/smartpot/com/api/Commands/Service/SCommand.java similarity index 96% rename from src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java rename to src/main/java/smartpot/com/api/Commands/Service/SCommand.java index 8c2ed73..c8a80ee 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommand.java +++ b/src/main/java/smartpot/com/api/Commands/Service/SCommand.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Commands.Model.DAO.Service; +package smartpot.com.api.Commands.Service; import lombok.Builder; import lombok.Data; @@ -7,10 +7,10 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import smartpot.com.api.Commands.Mapper.MCommand; -import smartpot.com.api.Commands.Model.DAO.Repository.RCommand; import smartpot.com.api.Commands.Model.DTO.CommandDTO; import smartpot.com.api.Commands.Model.Entity.Command; -import smartpot.com.api.Crops.Model.DAO.Service.SCropI; +import smartpot.com.api.Commands.Repository.RCommand; +import smartpot.com.api.Crops.Service.SCropI; import smartpot.com.api.Exception.ApiException; import smartpot.com.api.Exception.ApiResponse; diff --git a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java b/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java similarity index 88% rename from src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java rename to src/main/java/smartpot/com/api/Commands/Service/SCommandI.java index d12777d..682e4c7 100644 --- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java +++ b/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Commands.Model.DAO.Service; +package smartpot.com.api.Commands.Service; import smartpot.com.api.Commands.Model.DTO.CommandDTO; import smartpot.com.api.Commands.Model.Entity.Command; diff --git a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java index 5c18eee..2af2ca7 100644 --- a/src/main/java/smartpot/com/api/Crops/Controller/CropController.java +++ b/src/main/java/smartpot/com/api/Crops/Controller/CropController.java @@ -11,8 +11,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import smartpot.com.api.Crops.Model.DAO.Service.SCropI; import smartpot.com.api.Crops.Model.DTO.CropDTO; +import smartpot.com.api.Crops.Service.SCropI; import smartpot.com.api.Responses.ErrorResponse; import smartpot.com.api.Users.Model.DTO.UserDTO; diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java b/src/main/java/smartpot/com/api/Crops/Repository/RCrop.java similarity index 98% rename from src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java rename to src/main/java/smartpot/com/api/Crops/Repository/RCrop.java index 796b41d..22e5abf 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Repository/RCrop.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Crops.Model.DAO.Repository; +package smartpot.com.api.Crops.Repository; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Service/SCrop.java similarity index 99% rename from src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java rename to src/main/java/smartpot/com/api/Crops/Service/SCrop.java index e35429b..ff6dbc5 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Service/SCrop.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Crops.Model.DAO.Service; +package smartpot.com.api.Crops.Service; import jakarta.validation.ValidationException; import lombok.Builder; @@ -11,12 +11,12 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import smartpot.com.api.Crops.Mapper.MCrop; -import smartpot.com.api.Crops.Model.DAO.Repository.RCrop; import smartpot.com.api.Crops.Model.DTO.CropDTO; import smartpot.com.api.Crops.Model.Entity.CropStatus; import smartpot.com.api.Crops.Model.Entity.CropType; +import smartpot.com.api.Crops.Repository.RCrop; import smartpot.com.api.Crops.Validator.VCropI; -import smartpot.com.api.Users.Model.DAO.Service.SUserI; +import smartpot.com.api.Users.Service.SUserI; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java b/src/main/java/smartpot/com/api/Crops/Service/SCropI.java similarity index 93% rename from src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java rename to src/main/java/smartpot/com/api/Crops/Service/SCropI.java index 867b06c..f5d4b51 100644 --- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java +++ b/src/main/java/smartpot/com/api/Crops/Service/SCropI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Crops.Model.DAO.Service; +package smartpot.com.api.Crops.Service; import smartpot.com.api.Crops.Model.DTO.CropDTO; diff --git a/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java b/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java index 84c800a..3531193 100644 --- a/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java +++ b/src/main/java/smartpot/com/api/Mail/Controller/EmailController.java @@ -12,8 +12,8 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import smartpot.com.api.Mail.Model.DAO.Service.EmailServiceI; import smartpot.com.api.Mail.Model.DTO.EmailDTO; +import smartpot.com.api.Mail.Service.EmailServiceI; import smartpot.com.api.Responses.ErrorResponse; @RestController diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Repository/EmailRepository.java b/src/main/java/smartpot/com/api/Mail/Repository/EmailRepository.java similarity index 83% rename from src/main/java/smartpot/com/api/Mail/Model/DAO/Repository/EmailRepository.java rename to src/main/java/smartpot/com/api/Mail/Repository/EmailRepository.java index 06005d6..40cb3f3 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Repository/EmailRepository.java +++ b/src/main/java/smartpot/com/api/Mail/Repository/EmailRepository.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Mail.Model.DAO.Repository; +package smartpot.com.api.Mail.Repository; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java b/src/main/java/smartpot/com/api/Mail/Service/EmailService.java similarity index 97% rename from src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java rename to src/main/java/smartpot/com/api/Mail/Service/EmailService.java index 90c9fdd..5b93d44 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailService.java +++ b/src/main/java/smartpot/com/api/Mail/Service/EmailService.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Mail.Model.DAO.Service; +package smartpot.com.api.Mail.Service; import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; @@ -12,9 +12,9 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import smartpot.com.api.Mail.Mapper.EmailMapper; -import smartpot.com.api.Mail.Model.DAO.Repository.EmailRepository; import smartpot.com.api.Mail.Model.DTO.EmailDTO; import smartpot.com.api.Mail.Model.Entity.EmailDetails; +import smartpot.com.api.Mail.Repository.EmailRepository; import java.io.File; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java b/src/main/java/smartpot/com/api/Mail/Service/EmailServiceI.java similarity index 86% rename from src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java rename to src/main/java/smartpot/com/api/Mail/Service/EmailServiceI.java index 41f9c39..d1cf5b2 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DAO/Service/EmailServiceI.java +++ b/src/main/java/smartpot/com/api/Mail/Service/EmailServiceI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Mail.Model.DAO.Service; +package smartpot.com.api.Mail.Service; import smartpot.com.api.Mail.Model.DTO.EmailDTO; import smartpot.com.api.Mail.Model.Entity.EmailDetails; diff --git a/src/main/java/smartpot/com/api/Notifications/Controller/NotificationController.java b/src/main/java/smartpot/com/api/Notifications/Controller/NotificationController.java index 37a6acb..6919073 100644 --- a/src/main/java/smartpot/com/api/Notifications/Controller/NotificationController.java +++ b/src/main/java/smartpot/com/api/Notifications/Controller/NotificationController.java @@ -2,8 +2,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import smartpot.com.api.Notifications.Model.DAO.Service.SNotificationI; import smartpot.com.api.Notifications.Model.Entity.Notification; +import smartpot.com.api.Notifications.Service.SNotificationI; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Notifications/Model/DAO/Repository/RNotification.java b/src/main/java/smartpot/com/api/Notifications/Repository/RNotification.java similarity index 95% rename from src/main/java/smartpot/com/api/Notifications/Model/DAO/Repository/RNotification.java rename to src/main/java/smartpot/com/api/Notifications/Repository/RNotification.java index c5abe4e..00cd61b 100644 --- a/src/main/java/smartpot/com/api/Notifications/Model/DAO/Repository/RNotification.java +++ b/src/main/java/smartpot/com/api/Notifications/Repository/RNotification.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Notifications.Model.DAO.Repository; +package smartpot.com.api.Notifications.Repository; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/src/main/java/smartpot/com/api/Notifications/Model/DAO/Service/SNotification.java b/src/main/java/smartpot/com/api/Notifications/Service/SNotification.java similarity index 96% rename from src/main/java/smartpot/com/api/Notifications/Model/DAO/Service/SNotification.java rename to src/main/java/smartpot/com/api/Notifications/Service/SNotification.java index 3de3759..ed3eb83 100644 --- a/src/main/java/smartpot/com/api/Notifications/Model/DAO/Service/SNotification.java +++ b/src/main/java/smartpot/com/api/Notifications/Service/SNotification.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Notifications.Model.DAO.Service; +package smartpot.com.api.Notifications.Service; import lombok.Builder; import lombok.Data; @@ -8,8 +8,8 @@ import org.springframework.stereotype.Service; import smartpot.com.api.Exception.ApiException; import smartpot.com.api.Exception.ApiResponse; -import smartpot.com.api.Notifications.Model.DAO.Repository.RNotification; import smartpot.com.api.Notifications.Model.Entity.Notification; +import smartpot.com.api.Notifications.Repository.RNotification; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Notifications/Model/DAO/Service/SNotificationI.java b/src/main/java/smartpot/com/api/Notifications/Service/SNotificationI.java similarity index 89% rename from src/main/java/smartpot/com/api/Notifications/Model/DAO/Service/SNotificationI.java rename to src/main/java/smartpot/com/api/Notifications/Service/SNotificationI.java index 6fcea5f..46a4115 100644 --- a/src/main/java/smartpot/com/api/Notifications/Model/DAO/Service/SNotificationI.java +++ b/src/main/java/smartpot/com/api/Notifications/Service/SNotificationI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Notifications.Model.DAO.Service; +package smartpot.com.api.Notifications.Service; import smartpot.com.api.Notifications.Model.Entity.Notification; diff --git a/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java b/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java index 7cf7614..964982e 100644 --- a/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java +++ b/src/main/java/smartpot/com/api/Records/Controller/HistoryController.java @@ -5,10 +5,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import smartpot.com.api.Exception.ApiResponse; -import smartpot.com.api.Records.Model.DAO.Service.SHistoryI; import smartpot.com.api.Records.Model.DTO.RecordDTO; import smartpot.com.api.Records.Model.Entity.DateRange; import smartpot.com.api.Records.Model.Entity.History; +import smartpot.com.api.Records.Service.SHistoryI; import smartpot.com.api.Responses.ErrorResponse; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Records/Model/DAO/Repository/RHistory.java b/src/main/java/smartpot/com/api/Records/Repository/RHistory.java similarity index 94% rename from src/main/java/smartpot/com/api/Records/Model/DAO/Repository/RHistory.java rename to src/main/java/smartpot/com/api/Records/Repository/RHistory.java index 404292f..ea039ec 100644 --- a/src/main/java/smartpot/com/api/Records/Model/DAO/Repository/RHistory.java +++ b/src/main/java/smartpot/com/api/Records/Repository/RHistory.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Records.Model.DAO.Repository; +package smartpot.com.api.Records.Repository; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PastOrPresent; diff --git a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistory.java b/src/main/java/smartpot/com/api/Records/Service/SHistory.java similarity index 98% rename from src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistory.java rename to src/main/java/smartpot/com/api/Records/Service/SHistory.java index de4aea4..b6ed77e 100644 --- a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistory.java +++ b/src/main/java/smartpot/com/api/Records/Service/SHistory.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Records.Model.DAO.Service; +package smartpot.com.api.Records.Service; import lombok.Builder; import lombok.Data; @@ -8,17 +8,17 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import smartpot.com.api.Crops.Model.DAO.Service.SCropI; import smartpot.com.api.Crops.Model.DTO.CropDTO; +import smartpot.com.api.Crops.Service.SCropI; import smartpot.com.api.Exception.ApiException; import smartpot.com.api.Exception.ApiResponse; import smartpot.com.api.Records.Mapper.MRecords; -import smartpot.com.api.Records.Model.DAO.Repository.RHistory; import smartpot.com.api.Records.Model.DTO.CropRecordDTO; import smartpot.com.api.Records.Model.DTO.MeasuresDTO; import smartpot.com.api.Records.Model.DTO.RecordDTO; import smartpot.com.api.Records.Model.Entity.DateRange; import smartpot.com.api.Records.Model.Entity.History; +import smartpot.com.api.Records.Repository.RHistory; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistoryI.java b/src/main/java/smartpot/com/api/Records/Service/SHistoryI.java similarity index 94% rename from src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistoryI.java rename to src/main/java/smartpot/com/api/Records/Service/SHistoryI.java index d503274..1ba12e4 100644 --- a/src/main/java/smartpot/com/api/Records/Model/DAO/Service/SHistoryI.java +++ b/src/main/java/smartpot/com/api/Records/Service/SHistoryI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Records.Model.DAO.Service; +package smartpot.com.api.Records.Service; import org.springframework.http.ResponseEntity; import smartpot.com.api.Exception.ApiResponse; diff --git a/src/main/java/smartpot/com/api/Security/Headers/CorsConfig.java b/src/main/java/smartpot/com/api/Security/Config/CorsConfig.java similarity index 96% rename from src/main/java/smartpot/com/api/Security/Headers/CorsConfig.java rename to src/main/java/smartpot/com/api/Security/Config/CorsConfig.java index 3b23b90..61515db 100644 --- a/src/main/java/smartpot/com/api/Security/Headers/CorsConfig.java +++ b/src/main/java/smartpot/com/api/Security/Config/CorsConfig.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Security.Headers; +package smartpot.com.api.Security.Config; import jakarta.servlet.http.HttpServletRequest; import lombok.NonNull; diff --git a/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java b/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java index 4074b22..65536d7 100644 --- a/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java +++ b/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java @@ -16,9 +16,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import smartpot.com.api.Security.Filter.JwtAuthFilter; -import smartpot.com.api.Security.Headers.CorsConfig; -import smartpot.com.api.Users.Model.DAO.Service.SUser; +import smartpot.com.api.Security.Service.JwtAuthFilter; +import smartpot.com.api.Users.Service.SUser; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java b/src/main/java/smartpot/com/api/Security/Service/JwtAuthFilter.java similarity index 92% rename from src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java rename to src/main/java/smartpot/com/api/Security/Service/JwtAuthFilter.java index 1ae407c..4eb920e 100644 --- a/src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java +++ b/src/main/java/smartpot/com/api/Security/Service/JwtAuthFilter.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Security.Filter; +package smartpot.com.api.Security.Service; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -10,9 +10,8 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import smartpot.com.api.Security.Service.JwtService; -import smartpot.com.api.Users.Model.DAO.Service.SUser; import smartpot.com.api.Users.Model.DTO.UserDTO; +import smartpot.com.api.Users.Service.SUser; import java.io.IOException; diff --git a/src/main/java/smartpot/com/api/Security/Service/JwtService.java b/src/main/java/smartpot/com/api/Security/Service/JwtService.java index 5613723..b086689 100644 --- a/src/main/java/smartpot/com/api/Security/Service/JwtService.java +++ b/src/main/java/smartpot/com/api/Security/Service/JwtService.java @@ -10,10 +10,10 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import smartpot.com.api.Exception.InvalidTokenException; -import smartpot.com.api.Mail.Model.DAO.Service.EmailServiceI; import smartpot.com.api.Mail.Model.Entity.EmailDetails; -import smartpot.com.api.Users.Model.DAO.Service.SUserI; +import smartpot.com.api.Mail.Service.EmailServiceI; import smartpot.com.api.Users.Model.DTO.UserDTO; +import smartpot.com.api.Users.Service.SUserI; import javax.crypto.SecretKey; import java.util.Date; diff --git a/src/main/java/smartpot/com/api/Sessions/Controller/SessionController.java b/src/main/java/smartpot/com/api/Sessions/Controller/SessionController.java index d707d8f..68b1102 100644 --- a/src/main/java/smartpot/com/api/Sessions/Controller/SessionController.java +++ b/src/main/java/smartpot/com/api/Sessions/Controller/SessionController.java @@ -5,8 +5,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import smartpot.com.api.Responses.ErrorResponse; -import smartpot.com.api.Sessions.Model.DAO.Service.SSessionI; import smartpot.com.api.Sessions.Model.Entity.Session; +import smartpot.com.api.Sessions.Service.SSessionI; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Sessions/Model/DAO/Repository/RSession.java b/src/main/java/smartpot/com/api/Sessions/Repository/RSession.java similarity index 94% rename from src/main/java/smartpot/com/api/Sessions/Model/DAO/Repository/RSession.java rename to src/main/java/smartpot/com/api/Sessions/Repository/RSession.java index 495df7d..02a57e2 100644 --- a/src/main/java/smartpot/com/api/Sessions/Model/DAO/Repository/RSession.java +++ b/src/main/java/smartpot/com/api/Sessions/Repository/RSession.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Sessions.Model.DAO.Repository; +package smartpot.com.api.Sessions.Repository; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; diff --git a/src/main/java/smartpot/com/api/Sessions/Model/DAO/Service/SSession.java b/src/main/java/smartpot/com/api/Sessions/Service/SSession.java similarity index 97% rename from src/main/java/smartpot/com/api/Sessions/Model/DAO/Service/SSession.java rename to src/main/java/smartpot/com/api/Sessions/Service/SSession.java index af88b24..7e4388f 100644 --- a/src/main/java/smartpot/com/api/Sessions/Model/DAO/Service/SSession.java +++ b/src/main/java/smartpot/com/api/Sessions/Service/SSession.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Sessions.Model.DAO.Service; +package smartpot.com.api.Sessions.Service; import lombok.Builder; import lombok.Data; @@ -8,10 +8,10 @@ import org.springframework.stereotype.Service; import smartpot.com.api.Exception.ApiException; import smartpot.com.api.Exception.ApiResponse; -import smartpot.com.api.Sessions.Model.DAO.Repository.RSession; import smartpot.com.api.Sessions.Model.Entity.Session; -import smartpot.com.api.Users.Model.DAO.Service.SUserI; +import smartpot.com.api.Sessions.Repository.RSession; import smartpot.com.api.Users.Model.DTO.UserDTO; +import smartpot.com.api.Users.Service.SUserI; import java.util.Date; import java.util.List; diff --git a/src/main/java/smartpot/com/api/Sessions/Model/DAO/Service/SSessionI.java b/src/main/java/smartpot/com/api/Sessions/Service/SSessionI.java similarity index 90% rename from src/main/java/smartpot/com/api/Sessions/Model/DAO/Service/SSessionI.java rename to src/main/java/smartpot/com/api/Sessions/Service/SSessionI.java index 7204362..ddb8f71 100644 --- a/src/main/java/smartpot/com/api/Sessions/Model/DAO/Service/SSessionI.java +++ b/src/main/java/smartpot/com/api/Sessions/Service/SSessionI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Sessions.Model.DAO.Service; +package smartpot.com.api.Sessions.Service; import smartpot.com.api.Sessions.Model.Entity.Session; diff --git a/src/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java index 56612cd..c433630 100644 --- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java +++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java @@ -15,8 +15,8 @@ import org.springframework.web.bind.annotation.*; import smartpot.com.api.Responses.DeleteResponse; import smartpot.com.api.Responses.ErrorResponse; -import smartpot.com.api.Users.Model.DAO.Service.SUserI; import smartpot.com.api.Users.Model.DTO.UserDTO; +import smartpot.com.api.Users.Service.SUserI; /** * Controlador REST para las operaciones relacionadas con los usuarios. diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Repository/RUser.java b/src/main/java/smartpot/com/api/Users/Repository/RUser.java similarity index 98% rename from src/main/java/smartpot/com/api/Users/Model/DAO/Repository/RUser.java rename to src/main/java/smartpot/com/api/Users/Repository/RUser.java index 82b968e..4148188 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Repository/RUser.java +++ b/src/main/java/smartpot/com/api/Users/Repository/RUser.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Users.Model.DAO.Repository; +package smartpot.com.api.Users.Repository; import org.bson.types.ObjectId; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Service/SUser.java similarity index 99% rename from src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java rename to src/main/java/smartpot/com/api/Users/Service/SUser.java index 3d9f72c..c09bc0b 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Service/SUser.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Users.Model.DAO.Service; +package smartpot.com.api.Users.Service; import jakarta.validation.ValidationException; import lombok.Builder; @@ -13,9 +13,9 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import smartpot.com.api.Users.Mapper.MUser; -import smartpot.com.api.Users.Model.DAO.Repository.RUser; import smartpot.com.api.Users.Model.DTO.UserDTO; import smartpot.com.api.Users.Model.Entity.UserRole; +import smartpot.com.api.Users.Repository.RUser; import smartpot.com.api.Users.Validator.VUserI; import java.text.SimpleDateFormat; diff --git a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java b/src/main/java/smartpot/com/api/Users/Service/SUserI.java similarity index 98% rename from src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java rename to src/main/java/smartpot/com/api/Users/Service/SUserI.java index b08215e..a5c30ac 100644 --- a/src/main/java/smartpot/com/api/Users/Model/DAO/Service/SUserI.java +++ b/src/main/java/smartpot/com/api/Users/Service/SUserI.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Users.Model.DAO.Service; +package smartpot.com.api.Users.Service; import jakarta.validation.ValidationException; import org.springframework.security.core.userdetails.UserDetailsService; From 2452422ea7fc41928e3fa3e95ad5edc79020f65f Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Sun, 12 Jan 2025 19:38:37 -0500 Subject: [PATCH 23/25] command service refactoring --- .../Controller/CommandController.java | 51 +++++++++++++++---- .../com/api/Commands/Service/SCommand.java | 41 +++++++++++++-- .../com/api/Commands/Service/SCommandI.java | 6 ++- .../com/api/Mail/Model/DTO/EmailDTO.java | 4 +- 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java index eedaf9d..535b6de 100644 --- a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java +++ b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java @@ -14,10 +14,9 @@ import smartpot.com.api.Commands.Model.Entity.Command; import smartpot.com.api.Commands.Service.SCommandI; import smartpot.com.api.Crops.Model.DTO.CropDTO; +import smartpot.com.api.Responses.DeleteResponse; import smartpot.com.api.Responses.ErrorResponse; -import java.util.Date; - @RestController @RequestMapping("/Comandos") public class CommandController { @@ -92,8 +91,20 @@ public ResponseEntity<?> getUserById(@PathVariable String id) { } } - @PutMapping("/{id}/ejecutar") - public ResponseEntity<Command> executeCommand(@PathVariable String id) throws Exception { + @PutMapping("/{id}/run/{response}") + @Operation(summary = "Actualizar un comando a ejecutado", + description = "Actualiza los datos de un comando existente utilizando su ID. " + + "Si el comando no existe o hay un error, se devolverá un error con código HTTP 404.", + responses = { + @ApiResponse(description = "Comando actualizado", + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = CommandDTO.class))), + @ApiResponse(responseCode = "404", + description = "No se pudo actualizar el Comando. El Comando puede no existir o los datos pueden ser incorrectos.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity<?> executeCommand(@PathVariable String id, @PathVariable String response) { + /* Command command = serviceCommand.getCommandById(id); if (command != null) { command.setStatus("EXECUTED"); @@ -104,15 +115,33 @@ public ResponseEntity<Command> executeCommand(@PathVariable String id) throws Ex } else { return ResponseEntity.notFound().build(); } + + */ + + try { + return new ResponseEntity<>(serviceCommand.excuteCommand(id, response), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al actualizar el comando con ID '" + id + "' [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); + } } - @DeleteMapping("/delete/{id}") - public ResponseEntity<Object> deleteCommand(@PathVariable String id) { - if (serviceCommand.getCommandById(id) != null) { - serviceCommand.deleteCommand(id); - return ResponseEntity.ok().build(); - } else { - return ResponseEntity.notFound().build(); + @DeleteMapping("/Delete/{id}") + @Operation(summary = "Eliminar un Comando", + description = "Elimina un Comando existente utilizando su ID. " + + "Si el Comando no existe o hay un error, se devolverá un error con código HTTP 404.", + responses = { + @ApiResponse(description = "Comando eliminado", + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = DeleteResponse.class))), + @ApiResponse(responseCode = "404", + description = "No se pudo eliminar el Comando.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity<?> deleteCommand(@Parameter(description = "ID único del comando que se desea eliminar.", required = true) @PathVariable String id) { + try { + return new ResponseEntity<>(new DeleteResponse("Se ha eliminado un recurso [" + serviceCommand.deleteCommand(id) + "]"), HttpStatus.OK); + } catch (Exception e) { + return new ResponseEntity<>(new ErrorResponse("Error al actualizar el comando con ID '" + id + "' [" + e.getMessage() + "]", HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND); } } diff --git a/src/main/java/smartpot/com/api/Commands/Service/SCommand.java b/src/main/java/smartpot/com/api/Commands/Service/SCommand.java index c8a80ee..444c679 100644 --- a/src/main/java/smartpot/com/api/Commands/Service/SCommand.java +++ b/src/main/java/smartpot/com/api/Commands/Service/SCommand.java @@ -4,6 +4,9 @@ import lombok.Data; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import smartpot.com.api.Commands.Mapper.MCommand; @@ -41,8 +44,15 @@ public List<Command> getAllCommands() { } @Override - public Command getCommandById(String id) { - return repositoryCommand.findById(new ObjectId(id)).orElse(null); + @Cacheable(value = "commands", key = "'id_'+#id") + public CommandDTO getCommandById(String id) throws Exception { + return Optional.of(id) + .map(ObjectId::new) + .map(repositoryCommand::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .map(mapperCommand::toDTO) + .orElseThrow(() -> new Exception("El Comando no existe")); } @Override @@ -125,8 +135,31 @@ public Command updateCommand(String id, Command upCommand) throws Exception { } @Override - public void deleteCommand(String id) { - repositoryCommand.deleteById(new ObjectId(id)); + @CacheEvict(value = "commands", key = "'id_'+#id") + public String deleteCommand(String id) throws Exception { + return Optional.of(getCommandById(id)) + .map(command -> { + repositoryCommand.deleteById(new ObjectId(command.getId())); + return "El Comando con ID '" + id + "' fue eliminado."; + }) + .orElseThrow(() -> new Exception("El Comando no existe.")); + } + + @Override + @CachePut(value = "commands", key = "'id:'+#id") + public CommandDTO excuteCommand(String id, String response) throws Exception { + return Optional.of(getCommandById(id)) + .map(commandDTO -> { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + commandDTO.setDateCreated(formatter.format(new Date())); + commandDTO.setStatus("EXECUTED"); + commandDTO.setResponse(response); + return commandDTO; + }) + .map(mapperCommand::toEntity) + .map(repositoryCommand::save) + .map(mapperCommand::toDTO) + .orElseThrow(() -> new Exception("El Comando no se pudo actualizar")); } /* diff --git a/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java b/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java index 682e4c7..5a54198 100644 --- a/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java +++ b/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java @@ -8,11 +8,13 @@ public interface SCommandI { List<Command> getAllCommands(); - Command getCommandById(String id); + CommandDTO getCommandById(String id) throws Exception; CommandDTO createCommand(CommandDTO newCommand); Command updateCommand(String id, Command upCommand) throws Exception; - void deleteCommand(String id); + String deleteCommand(String id) throws Exception; + + CommandDTO excuteCommand(String id, String reponse) throws Exception; } diff --git a/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java b/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java index 22b8104..43c81e9 100644 --- a/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java +++ b/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java @@ -4,10 +4,12 @@ import lombok.Data; import lombok.RequiredArgsConstructor; +import java.io.Serializable; + @Data @AllArgsConstructor @RequiredArgsConstructor -public class EmailDTO { +public class EmailDTO implements Serializable { private String id; private String recipient; private String msgBody; From 89360baedb8ec7069fa0c3ffa76f18c73078e777 Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Tue, 14 Jan 2025 21:15:01 -0500 Subject: [PATCH 24/25] Rate Limiting Config --- .env.example | 5 ++ .../Filters}/JwtAuthFilter.java | 3 +- .../Config/Filters/RateLimitingFilter.java | 48 +++++++++++++++++++ .../Config/SecurityConfiguration.java | 3 +- .../Config/{ => headers}/CorsConfig.java | 2 +- src/main/resources/application.properties | 3 ++ 6 files changed, 61 insertions(+), 3 deletions(-) rename src/main/java/smartpot/com/api/Security/{Service => Config/Filters}/JwtAuthFilter.java (94%) create mode 100644 src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java rename src/main/java/smartpot/com/api/Security/Config/{ => headers}/CorsConfig.java (95%) diff --git a/.env.example b/.env.example index 96ca01c..9854e30 100644 --- a/.env.example +++ b/.env.example @@ -50,6 +50,11 @@ SECURITY_JWT_SECRET_KEY=<JWT_SECRET_KEY> # Secret key for signing JWT tokens SECURITY_JWT_EXPIRATION=<JWT_EXPIRATION> # JWT expiration time (in ms) SECURITY_PUBLIC_ROUTES=<PUBLIC_ROUTES> # Public routes that do not require authentication (e.g., /auth/login) +# Rate Limiting Config +# Settings for API rate limiting +RATE_LIMITING_MAX_REQUESTS=5 # Max requests per client IP within the defined time window +RATE_LIMITING_TIME_WINDOW=60000 # Time window in milliseconds (1 minute) + # HTTPS Headers (CORS) # Settings for Cross-Origin Resource Sharing (CORS) HEADER_CORS_ALLOWED_ORIGINS=<ALLOWED_ORIGINS> # Allowed origins for CORS (e.g., http://localhost:3000) diff --git a/src/main/java/smartpot/com/api/Security/Service/JwtAuthFilter.java b/src/main/java/smartpot/com/api/Security/Config/Filters/JwtAuthFilter.java similarity index 94% rename from src/main/java/smartpot/com/api/Security/Service/JwtAuthFilter.java rename to src/main/java/smartpot/com/api/Security/Config/Filters/JwtAuthFilter.java index 4eb920e..9308d00 100644 --- a/src/main/java/smartpot/com/api/Security/Service/JwtAuthFilter.java +++ b/src/main/java/smartpot/com/api/Security/Config/Filters/JwtAuthFilter.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Security.Service; +package smartpot.com.api.Security.Config.Filters; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -10,6 +10,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import smartpot.com.api.Security.Service.JwtService; import smartpot.com.api.Users.Model.DTO.UserDTO; import smartpot.com.api.Users.Service.SUser; diff --git a/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java b/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java new file mode 100644 index 0000000..4eea454 --- /dev/null +++ b/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java @@ -0,0 +1,48 @@ +package smartpot.com.api.Security.Config.Filters; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +@Component +public class RateLimitingFilter implements Filter { + + private final Map<String, AtomicInteger> requestsCount = new ConcurrentHashMap<>(); + + @Value("${rate.limiting.max-requests}") + private int MAX_REQUESTS; + + @Value("${rate.limiting.time-window}") + private long TIME_WINDOW; + + private long windowStart = System.currentTimeMillis(); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String clientIP = request.getRemoteAddr(); + long currentTime = System.currentTimeMillis(); + + if (currentTime - windowStart > TIME_WINDOW) { + windowStart = currentTime; + requestsCount.clear(); + } + + requestsCount.putIfAbsent(clientIP, new AtomicInteger(0)); + int currentCount = requestsCount.get(clientIP).incrementAndGet(); + + if (currentCount >= MAX_REQUESTS) { + ((HttpServletResponse) response).setStatus(429); + response.getWriter().write("Haz enviado demasiadas solicitudes, intenta de nuevo mas tarde"); + return; + } + + chain.doFilter(request, response); + } +} diff --git a/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java b/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java index 65536d7..a26ff88 100644 --- a/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java +++ b/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java @@ -16,7 +16,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import smartpot.com.api.Security.Service.JwtAuthFilter; +import smartpot.com.api.Security.Config.headers.CorsConfig; +import smartpot.com.api.Security.Config.Filters.JwtAuthFilter; import smartpot.com.api.Users.Service.SUser; import java.util.Arrays; diff --git a/src/main/java/smartpot/com/api/Security/Config/CorsConfig.java b/src/main/java/smartpot/com/api/Security/Config/headers/CorsConfig.java similarity index 95% rename from src/main/java/smartpot/com/api/Security/Config/CorsConfig.java rename to src/main/java/smartpot/com/api/Security/Config/headers/CorsConfig.java index 61515db..9cc324c 100644 --- a/src/main/java/smartpot/com/api/Security/Config/CorsConfig.java +++ b/src/main/java/smartpot/com/api/Security/Config/headers/CorsConfig.java @@ -1,4 +1,4 @@ -package smartpot.com.api.Security.Config; +package smartpot.com.api.Security.Config.headers; import jakarta.servlet.http.HttpServletRequest; import lombok.NonNull; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ac2a7dc..458ef14 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -31,6 +31,9 @@ spring.cache.type=${CACHE_TYPE} spring.cache.redis.time-to-live=${CACHE_TIME_TO_LIVE} spring.cache.redis.cache-null-values=${CACHE_NULL_VALUES} +# Configuración de Rate Limiting +rate.limiting.max-requests=${RATE_LIMITING_MAX_REQUESTS} +rate.limiting.time-window=${RATE_LIMITING_TIME_WINDOW} # Swagger Config From d6e5895c82175fb546f296a828940a08edd89086 Mon Sep 17 00:00:00 2001 From: SebastianLopezO <sebastianlopezosorno2005@gmail.com> Date: Tue, 14 Jan 2025 21:29:53 -0500 Subject: [PATCH 25/25] configuration of routes exempt from rate limiting --- .env.example | 5 +-- .../Config/Filters/RateLimitingFilter.java | 35 +++++++++++++++++-- .../Config/SecurityConfiguration.java | 1 - src/main/resources/application.properties | 2 +- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 9854e30..4c5e64f 100644 --- a/.env.example +++ b/.env.example @@ -52,8 +52,9 @@ SECURITY_PUBLIC_ROUTES=<PUBLIC_ROUTES> # Public routes that do not require au # Rate Limiting Config # Settings for API rate limiting -RATE_LIMITING_MAX_REQUESTS=5 # Max requests per client IP within the defined time window -RATE_LIMITING_TIME_WINDOW=60000 # Time window in milliseconds (1 minute) +RATE_LIMITING_MAX_REQUESTS=<MAX_REQUESTS> # Max requests per client IP within the defined time window +RATE_LIMITING_TIME_WINDOW=<TIME_WINDOW> # Time window in milliseconds (1 minute) +RATE_LIMITING_PUBLIC_ROUTES=<PUBLIC_ROUTES> # Public routes excluded from rate limiting # HTTPS Headers (CORS) # Settings for Cross-Origin Resource Sharing (CORS) diff --git a/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java b/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java index 4eea454..fc38844 100644 --- a/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java +++ b/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java @@ -7,6 +7,8 @@ import org.springframework.stereotype.Component; import java.io.IOException; +import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -22,10 +24,30 @@ public class RateLimitingFilter implements Filter { @Value("${rate.limiting.time-window}") private long TIME_WINDOW; + @Value("${rate.limiting.public-routes}") + private String publicRoutes; + private long windowStart = System.currentTimeMillis(); + private List<String> publicRoutesList; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Convertir el string de rutas públicas en una lista + if (publicRoutes != null && !publicRoutes.isEmpty()) { + publicRoutesList = Arrays.asList(publicRoutes.split(",")); + } + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + + if (isPublicRoute(httpRequest.getRequestURI())) { + chain.doFilter(request, response); + return; + } + String clientIP = request.getRemoteAddr(); long currentTime = System.currentTimeMillis(); @@ -38,11 +60,20 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha int currentCount = requestsCount.get(clientIP).incrementAndGet(); if (currentCount >= MAX_REQUESTS) { - ((HttpServletResponse) response).setStatus(429); - response.getWriter().write("Haz enviado demasiadas solicitudes, intenta de nuevo mas tarde"); + ((HttpServletResponse) response).setStatus(429); // Too Many Requests + response.getWriter().write("Haz enviado demasiadas solicitudes, intenta de nuevo más tarde"); return; } chain.doFilter(request, response); } + + private boolean isPublicRoute(String uri) { + for (String route : publicRoutesList) { + if (uri.startsWith(route)) { + return true; + } + } + return false; + } } diff --git a/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java b/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java index a26ff88..2f4d12b 100644 --- a/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java +++ b/src/main/java/smartpot/com/api/Security/Config/SecurityConfiguration.java @@ -51,7 +51,6 @@ public SecurityConfiguration(CorsConfig corsConfig, JwtAuthFilter jwtAuthFilter, @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSec) throws Exception { - // Public Routes List<String> publicRoutesList; if (publicRoutes.contains(",")) { publicRoutesList = Arrays.asList(publicRoutes.split(",")); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 458ef14..9eba63c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,7 +34,7 @@ spring.cache.redis.cache-null-values=${CACHE_NULL_VALUES} # Configuración de Rate Limiting rate.limiting.max-requests=${RATE_LIMITING_MAX_REQUESTS} rate.limiting.time-window=${RATE_LIMITING_TIME_WINDOW} - +rate.limiting.public-routes=${RATE_LIMITING_PUBLIC_ROUTES} # Swagger Config springdoc.api-docs.enabled=true