From e55199e9ec34503933cef405a1bdfa4f340b9d15 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Sat, 18 Jan 2025 21:57:17 -0500 Subject: [PATCH 1/5] Add Javadoc documentation for Command service and interface - Added detailed Javadoc documentation for the `SCommand` service class, including its methods and dependencies. - Documented the `SCommandI` interface, providing a thorough description of its purpose, responsibilities, and methods. - Ensured all methods in the service and interface are well-documented with parameter details, return types, exceptions, and usage context. - Improved code readability and maintainability through consistent comments and structured documentation. --- .../Controller/CommandController.java | 36 +-- .../com/api/Commands/Service/SCommand.java | 259 +++++++++++------- .../com/api/Commands/Service/SCommandI.java | 88 +++++- 3 files changed, 256 insertions(+), 127 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 535b6de..7ac1d11 100644 --- a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java +++ b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java @@ -11,7 +11,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; 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.DeleteResponse; @@ -104,22 +103,8 @@ public ResponseEntity getUserById(@PathVariable String id) { 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"); - command.setDateExecuted(new Date()); - command.setResponse("SUCCESSFUL"); - Command updatedCommand = serviceCommand.updateCommand(id, command); - return ResponseEntity.ok(updatedCommand); - } else { - return ResponseEntity.notFound().build(); - } - - */ - try { - return new ResponseEntity<>(serviceCommand.excuteCommand(id, response), HttpStatus.OK); + return new ResponseEntity<>(serviceCommand.executeCommand(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); } @@ -146,8 +131,23 @@ public ResponseEntity deleteCommand(@Parameter(description = "ID único del c } @PutMapping("/Update/{id}") - public Command updateCommand(@PathVariable String id, @RequestBody Command updatedCommad) throws Exception { - return serviceCommand.updateCommand(id, updatedCommad); + @Operation(summary = "Eliminar un comando", + description = "Elimina un comando existente utilizando su ID. " + + "Si el comando no existe o hay un error en el proceso, se devolverá un error con el código HTTP 404.", + responses = { + @ApiResponse(description = "Comando eliminado", + responseCode = "204", + content = @Content(mediaType = "application/json")), + @ApiResponse(responseCode = "404", + description = "Comando no encontrado o error en la eliminación.", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity updateCommand(@PathVariable String id, @RequestBody CommandDTO updatedCommand) { + try { + return new ResponseEntity<>(new DeleteResponse("Se ha eliminado un recurso [" + serviceCommand.updateCommand(id, updatedCommand) + "]"), 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 444c679..afd6730 100644 --- a/src/main/java/smartpot/com/api/Commands/Service/SCommand.java +++ b/src/main/java/smartpot/com/api/Commands/Service/SCommand.java @@ -7,21 +7,59 @@ 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; import smartpot.com.api.Commands.Model.DTO.CommandDTO; -import smartpot.com.api.Commands.Model.Entity.Command; 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; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Optional; - +import java.util.stream.Collectors; + +/** + * Service implementation for managing commands. + * + *

+ * This class provides the business logic for managing commands, including + * CRUD operations and execution logic. It interacts with the repository, + * a mapper, and additional services to fulfill its responsibilities. + *

+ * + *

Annotations:

+ * + * + *

Dependencies:

+ * + * + *

Responsibilities:

+ * + * + *

Usage:

+ *

+ * This service is typically used by controllers to handle HTTP requests related to commands, + * or by other services that depend on command-related operations. + *

+ * + * @see SCommandI + * @see RCommand + * @see SCropI + * @see MCommand + */ @Data @Builder @Service @@ -31,6 +69,13 @@ public class SCommand implements SCommandI { private final SCropI serviceCrop; private final MCommand mapperCommand; + /** + * Constructs an instance of {@code SCommand} with the required dependencies. + * + * @param repositoryCommand the repository for command-related database operations + * @param serviceCrop the service responsible for crop-related logic + * @param mapperCommand the mapper for converting entities to DTOs and vice versa + */ @Autowired public SCommand(RCommand repositoryCommand, SCropI serviceCrop, MCommand mapperCommand) { this.repositoryCommand = repositoryCommand; @@ -38,11 +83,38 @@ public SCommand(RCommand repositoryCommand, SCropI serviceCrop, MCommand mapperC this.mapperCommand = mapperCommand; } + /** + * Retrieves all commands from the repository and maps them to DTOs. + *

+ * This method is cached to improve performance when retrieving the list of commands. + * The cache is identified by the key 'all_commands' under the 'commands' cache. + *

+ * + * @return a list of {@code CommandDTO} objects representing all commands + * @throws Exception if no commands exist in the repository + */ @Override - public List getAllCommands() { - return repositoryCommand.findAll(); + @Cacheable(value = "commands", key = "'all_commands'") + public List getAllCommands() throws Exception { + return Optional.of(repositoryCommand.findAll()) + .filter(commands -> !commands.isEmpty()) + .map(crops -> crops.stream() + .map(mapperCommand::toDTO) + .collect(Collectors.toList())) + .orElseThrow(() -> new Exception("No existe ningún comando")); } + /** + * Retrieves a command by its unique identifier and maps it to a DTO. + *

+ * This method is cached to improve performance for retrieving individual commands. + * The cache is identified by the key pattern 'id_{id}' under the 'commands' cache. + *

+ * + * @param id the unique identifier of the command to retrieve + * @return a {@code CommandDTO} representing the command with the specified ID + * @throws Exception if the command with the specified ID does not exist + */ @Override @Cacheable(value = "commands", key = "'id_'+#id") public CommandDTO getCommandById(String id) throws Exception { @@ -55,6 +127,19 @@ public CommandDTO getCommandById(String id) throws Exception { .orElseThrow(() -> new Exception("El Comando no existe")); } + /** + * Creates a new command based on the provided DTO, sets its creation date and status, + * and persists it in the repository. + * + *

+ * This method assigns a default status of "PENDING" and records the current timestamp + * as the creation date in the format "yyyy-MM-dd HH:mm:ss". + *

+ * + * @param commandDTO the {@code CommandDTO} containing the details of the command to create + * @return a {@code CommandDTO} representing the created command + * @throws IllegalStateException if a command with the same details already exists + */ @Override public CommandDTO createCommand(CommandDTO commandDTO) throws IllegalStateException { return Optional.of(commandDTO) @@ -70,84 +155,22 @@ public CommandDTO createCommand(CommandDTO commandDTO) throws IllegalStateExcept .orElseThrow(() -> new IllegalStateException("El Comando ya existe")); } - @Override - 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).", - HttpStatus.BAD_REQUEST.value() - )); - } - Command exCommand = repositoryCommand.findById(new ObjectId(id)).orElseThrow(() -> new ApiException( - new ApiResponse("El Comando con ID '" + id + "' no fue encontrado.", - HttpStatus.NOT_FOUND.value()) - )); - - if (upCommand == null) { - throw new IllegalArgumentException("El comando de actualización no puede ser nulo"); - } - - if (upCommand.getCrop() == null && serviceCrop.getCropById(upCommand.getCrop().toString()) != null) { - throw new IllegalArgumentException("El campo 'crop' no puede ser nulo"); - } - - if (upCommand.getDateCreated() == null) { - throw new IllegalArgumentException("El campo 'dateCreated' no puede ser nulo"); - } - - // Validar y convertir commandType a mayúsculas - if (upCommand.getCommandType() == null || upCommand.getCommandType().isEmpty()) { - throw new IllegalArgumentException("El campo 'commandType' no puede estar vacío"); - } else { - exCommand.setCommandType(upCommand.getCommandType().toUpperCase()); - } - - // Validar y convertir status a mayúsculas - if (upCommand.getStatus() == null || upCommand.getStatus().isEmpty()) { - throw new IllegalArgumentException("El campo 'status' no puede estar vacío"); - } else { - exCommand.setStatus(upCommand.getStatus().toUpperCase()); - } - - // Si se cumplen todas las validaciones, se procede a actualizar el comando - if (exCommand != null) { - exCommand.setCrop(upCommand.getCrop()); - exCommand.setResponse(upCommand.getResponse()); - exCommand.setDateCreated(upCommand.getDateCreated()); - return repositoryCommand.save(exCommand); - } else { - return null; - } - /* - - - if (!upCommand.getCommandType().matches(exCommand.getCommandType())) { - throw new Exception(new ErrorResponse( - "El nombre '" + upCommand.getCommandType() + "' no es válido.", - HttpStatus.BAD_REQUEST.value() - )); - } - if (!upCommand.ge) { - } - */ - - - } - - @Override - @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.")); - } - + /** + * Executes a command by updating its status to "EXECUTED" and setting a response message. + * + *

+ * This method updates the specified command with the current timestamp, sets its status to + * "EXECUTED", and records the provided response message. + *

+ * + * @param id the ID of the command to execute + * @param response the response message to associate with the executed command + * @return a {@code CommandDTO} representing the updated command + * @throws Exception if the command cannot be found or updated + */ @Override @CachePut(value = "commands", key = "'id:'+#id") - public CommandDTO excuteCommand(String id, String response) throws Exception { + public CommandDTO executeCommand(String id, String response) throws Exception { return Optional.of(getCommandById(id)) .map(commandDTO -> { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -162,31 +185,57 @@ public CommandDTO excuteCommand(String id, String response) throws Exception { .orElseThrow(() -> new Exception("El Comando no se pudo actualizar")); } -/* - - - - - public Command executeCommand(String id, String response) { - Command command = repositoryCommand.findById(id).orElse(null); - if (command != null) { - command.setStatus("EXECUTED"); - command.setDateExecuted(new Date()); - command.setResponse(response); - return repositoryCommand.save(command); - } - return null; + /** + * Updates the specified command with new details provided in the update DTO. + * + *

+ * This method updates fields in the command only if the corresponding fields in the + * update DTO are non-null. The existing values are retained for fields that are null in the update DTO. + *

+ * + * @param id the ID of the command to update + * @param updateCommand the {@code CommandDTO} containing the updated details + * @return a {@code CommandDTO} representing the updated command + * @throws Exception if the command cannot be found or updated + */ + @Override + @CachePut(value = "commands", key = "'id_'+#id") + public CommandDTO updateCommand(String id, CommandDTO updateCommand) throws Exception { + CommandDTO existingCommand = getCommandById(id); + return Optional.of(updateCommand) + .map(dto -> { + existingCommand.setId(dto.getCommandType() != null ? dto.getCommandType() : existingCommand.getCommandType()); + existingCommand.setResponse(dto.getResponse() != null ? dto.getResponse() : existingCommand.getResponse()); + existingCommand.setCrop(dto.getCrop() != null ? dto.getCrop() : existingCommand.getCrop()); + return existingCommand; + }) + .map(mapperCommand::toEntity) + .map(repositoryCommand::save) + .map(mapperCommand::toDTO) + .orElseThrow(() -> new Exception("El Comando no se pudo actualizar")); } - - - public List getCommandsByStatus(String status) { - return repositoryCommand.findByStatus(status); + /** + * Deletes the specified command by its ID. + * + *

+ * This method removes the command from the repository and evicts the corresponding cache entry. + *

+ * + * @param id the ID of the command to delete + * @return a confirmation message indicating the successful deletion + * @throws Exception if the command cannot be found + */ + @Override + @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.")); } - public List getCommandsByCropId(String cropId) { - return repositoryCommand.findByCrop_Id(cropId); - } - */ } 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 5a54198..d0ad4bb 100644 --- a/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java +++ b/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java @@ -1,20 +1,100 @@ package smartpot.com.api.Commands.Service; import smartpot.com.api.Commands.Model.DTO.CommandDTO; -import smartpot.com.api.Commands.Model.Entity.Command; import java.util.List; +/** + * Interface for the Command Service. + * + *

+ * Defines the contract for managing command-related operations, including + * CRUD operations and execution logic. Implementations of this interface + * are expected to provide the core functionality for working with commands. + *

+ * + *

Responsibilities:

+ *
    + *
  • Retrieve all commands from the data source.
  • + *
  • Retrieve a specific command by its ID.
  • + *
  • Create a new command with specified details.
  • + *
  • Update the details of an existing command.
  • + *
  • Delete a command by its ID.
  • + *
  • Execute a command and record its response.
  • + *
+ * + *

Methods:

+ *
    + *
  • {@link #getAllCommands()} - Fetches all available commands.
  • + *
  • {@link #getCommandById(String)} - Retrieves a command by its unique ID.
  • + *
  • {@link #createCommand(CommandDTO)} - Creates a new command in the system.
  • + *
  • {@link #updateCommand(String, CommandDTO)} - Updates the details of a command.
  • + *
  • {@link #deleteCommand(String)} - Deletes a command by its ID.
  • + *
  • {@link #executeCommand(String, String)} - Executes a command and logs its response.
  • + *
+ * + *

Usage:

+ *

+ * This interface is typically implemented by a service class, such as {@code SCommand}, + * to provide the actual logic for managing commands. It is used by controllers + * or other services to interact with command-related functionality. + *

+ * + * @see SCommand + * @see CommandDTO + */ public interface SCommandI { - List getAllCommands(); + /** + * Retrieves all commands available in the system. + * + * @return a list of {@link CommandDTO} representing all commands. + * @throws Exception if an error occurs during retrieval or if no commands exist. + */ + List getAllCommands() throws Exception; + /** + * Retrieves a specific command by its ID. + * + * @param id the unique identifier of the command. + * @return the {@link CommandDTO} representing the command. + * @throws Exception if the command does not exist or retrieval fails. + */ CommandDTO getCommandById(String id) throws Exception; + /** + * Creates a new command in the system. + * + * @param newCommand the {@link CommandDTO} containing the command details. + * @return the created {@link CommandDTO}. + */ CommandDTO createCommand(CommandDTO newCommand); - Command updateCommand(String id, Command upCommand) throws Exception; + /** + * Updates the details of an existing command. + * + * @param id the unique identifier of the command to update. + * @param updateCommand the {@link CommandDTO} containing updated details. + * @return the updated {@link CommandDTO}. + * @throws Exception if the command does not exist or the update fails. + */ + CommandDTO updateCommand(String id, CommandDTO updateCommand) throws Exception; + /** + * Deletes a command by its unique ID. + * + * @param id the unique identifier of the command to delete. + * @return a message confirming the deletion. + * @throws Exception if the command does not exist or the deletion fails. + */ String deleteCommand(String id) throws Exception; - CommandDTO excuteCommand(String id, String reponse) throws Exception; + /** + * Executes a command and records its response. + * + * @param id the unique identifier of the command to execute. + * @param response the response or result of the command execution. + * @return the updated {@link CommandDTO} reflecting the execution details. + * @throws Exception if the command does not exist or execution fails. + */ + CommandDTO executeCommand(String id, String response) throws Exception; } From 81c711e905b056e9a436f3f078734d819daa6d68 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Fri, 31 Jan 2025 22:25:46 -0500 Subject: [PATCH 2/5] Migration of properties to YML format. --- src/main/resources/application.properties | 84 ------------- src/main/resources/application.yml | 138 ++++++++++++++++++++++ 2 files changed, 138 insertions(+), 84 deletions(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 64a8408..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,84 +0,0 @@ -# Configuración APP -spring.application.name=${APP_NAME} -server.port=${PORT} -application.title=${TITLE} -application.description=${DESCRIPTION} -application.version=${VERSION} -application.author=${AUTHOR} - -# Conexión de MongoDB -spring.data.mongodb.uri=${DATA_CONNECTION_METHOD}://${DATA_SOURCE_USERNAME}:${DATA_SOURCE_PASSWORD}@${DATA_SOURCE_DOMAIN}/${DATA_SOURCE_DB}?${DATA_PARAMS} - -# Security Config -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} -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} - -# 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 -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 - -# 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} - -# Config TOMCAT -server.tomcat.connection-timeout=${SERVER_TOMCAT_TIMEOUT} - -# Logs - -# Activar logs de seguridad para JWT -logging.level.org.springframework.security=${DEBUGGER_MODE} -logging.level.smartpot.com.api.Security.jwt=${DEBUGGER_MODE} - -# Habilitar logs de headers HTTP y filtros -logging.level.org.springframework.web=${DEBUGGER_MODE} -logging.level.org.springframework.web.filter=${DEBUGGER_MODE} - -# Logs de MongoDB -logging.level.org.springframework.data.mongodb=${DEBUGGER_MODE} -logging.level.com.mongodb=${DEBUGGER_MODE} - -# Logs de Swagger -logging.level.org.springdoc=${DEBUGGER_MODE} -logging.level.io.swagger=${DEBUGGER_MODE} - -# Logs generales de Spring Boot -logging.level.org.springframework=${DEBUGGER_MODE} -logging.level.root=${DEBUGGER_MODE} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..c76226b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,138 @@ +# Información de la aplicación +application: + # Autor de la aplicación + author: ${AUTHOR} + # Descripción de la aplicación + description: ${DESCRIPTION} + security: + jwt: + # Tiempo de expiración del JWT + expiration: ${SECURITY_JWT_EXPIRATION} + # Clave secreta para la firma del JWT + secret-key: ${SECURITY_JWT_SECRET_KEY} + public: + # Rutas públicas que no requieren autenticación + routes: ${SECURITY_PUBLIC_ROUTES} + # Título de la aplicación + title: ${TITLE} + # Versión de la aplicación + version: ${VERSION} + +# Configuración HTTP +http: + header: + cors: + # Orígenes permitidos para CORS (Cross-Origin Resource Sharing) + allowedOrigins: ${HEADER_CORS_ALLOWED_ORIGINS} + +# Configuración de logging +logging: + level: + com: + # Nivel de log para la biblioteca 'mongodb' + mongodb: ${DEBUGGER_MODE} + io: + # Nivel de log para la biblioteca 'swagger' + swagger: ${DEBUGGER_MODE} + org: + # Niveles de log para diversas bibliotecas dentro de Spring + springdoc: ${DEBUGGER_MODE} + springframework: ${DEBUGGER_MODE} + springframework.data.mongodb: ${DEBUGGER_MODE} + root: + # Nivel de log para el nivel raíz + root: ${DEBUGGER_MODE} + smartpot: + com: + api: + Security: + jwt: ${DEBUGGER_MODE} # Nivel de log para JWT en la API de seguridad + +# Configuración de limitación de tasa (Rate Limiting) +rate: + limiting: + # Número máximo de solicitudes permitidas + max-requests: ${RATE_LIMITING_MAX_REQUESTS} + # Rutas públicas que estarán sujetas a la limitación de tasa + public-routes: ${RATE_LIMITING_PUBLIC_ROUTES} + # Ventana de tiempo en la que se aplican las limitaciones + time-window: ${RATE_LIMITING_TIME_WINDOW} + +# Configuración del servidor +server: + # Puerto en el que la aplicación escucha + port: ${PORT} + tomcat: + # Tiempo de espera para las conexiones de Tomcat + connection-timeout: ${SERVER_TOMCAT_TIMEOUT} + +# Configuración de Spring +spring: + application: + # Nombre de la aplicación + name: ${APP_NAME} + cache: + redis: + # Configuración de caché en Redis + cache-null-values: ${CACHE_NULL_VALUES} + # Tiempo de vida de los valores en caché + time-to-live: ${CACHE_TIME_TO_LIVE} + # Tipo de caché (por ejemplo, redis, ehcache, etc.) + type: ${CACHE_TYPE} + data: + mongodb: + # URI de conexión a MongoDB + uri: ${DATA_CONNECTION_METHOD}://${DATA_SOURCE_USERNAME}:${DATA_SOURCE_PASSWORD}@${DATA_SOURCE_DOMAIN}/${DATA_SOURCE_DB}?${DATA_PARAMS} + redis: + # Configuración de la base de datos Redis + database: ${CACHE_DB} + host: ${CACHE_HOST} + lettuce: + pool: + # Configuración de la conexión en pool para Redis + max-active: ${CACHE_LETTUCE_POOL_MAX_ACTIVE} + max-idle: ${CACHE_LETTUCE_POOL_MAX_IDLE} + max-wait: ${CACHE_LETTUCE_POOL_MAX_WAIT} + min-idle: ${CACHE_LETTUCE_POOL_MIN_IDLE} + # Configuración de la autenticación en Redis + password: ${CACHE_PASSWORD} + port: ${CACHE_PORT} + timeout: ${CACHE_TIMEOUT} + username: ${CACHE_USERNAME} + mail: + # Configuración para el servidor de correo + host: ${MAIL_HOST} + password: ${MAIL_PASSWORD} + port: ${MAIL_PORT} + properties: + mail: + smtp: + # Configuración de autenticación SMTP + auth: ${MAIL_PROPERTIES_SMTP_AUTH} + # Habilitar STARTTLS para la conexión segura + starttls: + enable: ${MAIL_PROPERTIES_SMTP_STARTTLS_ENABLE} + username: ${MAIL_USERNAME} + +# Configuración de Swagger/OpenAPI (SpringDoc) +springdoc: + api-docs: + # Habilitar o deshabilitar la generación de la documentación de la API + enabled: true + swagger-ui: + # Profundidad de expansión de modelos en Swagger UI + default-model-expand-depth: 1 + # Tipo de renderizado de modelos (puede ser 'example', 'model', etc.) + default-model-rendering: example + # Mostrar o no el ID de la operación en la UI + display-operation-id: false + # Mostrar la duración de las solicitudes en la UI de Swagger + display-request-duration: true + # Expansión por defecto en Swagger UI (puede ser 'list', 'full', etc.) + doc-expansion: list + # Habilitar o deshabilitar la UI de Swagger + enabled: true + # Path para acceder a Swagger UI + path: / + # URL donde se encuentra la documentación de la API en formato OpenAPI + url: /v3/api-docs From 1febda10c1d3eb5d6eb4aa4dcb5c4858c866c529 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Fri, 31 Jan 2025 22:26:13 -0500 Subject: [PATCH 3/5] Implementation of transactional functions in student service --- .../smartpot/com/api/Users/Service/SUser.java | 41 ++++++++++++++++--- .../com/api/Users/Service/SUserI.java | 2 +- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/main/java/smartpot/com/api/Users/Service/SUser.java b/src/main/java/smartpot/com/api/Users/Service/SUser.java index c09bc0b..ecf8149 100644 --- a/src/main/java/smartpot/com/api/Users/Service/SUser.java +++ b/src/main/java/smartpot/com/api/Users/Service/SUser.java @@ -12,6 +12,7 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import smartpot.com.api.Users.Mapper.MUser; import smartpot.com.api.Users.Model.DTO.UserDTO; import smartpot.com.api.Users.Model.Entity.UserRole; @@ -56,24 +57,33 @@ public SUser(RUser repositoryUser, MUser mapperUser, VUserI validatorUser) { /** * Crea un nuevo usuario en la base de datos a partir de un objeto {@link UserDTO}. - * * + *

* Este método valida que el usuario no exista previamente en la base de datos mediante su email. * Luego, realiza una serie de validaciones sobre los datos del usuario, como el nombre, apellido, * correo electrónico, contraseña y rol. Si las validaciones son exitosas, el usuario se crea y * se guarda en la base de datos. Si el usuario ya existe o si las validaciones fallan, se lanza una * excepción correspondiente. - * * - * + *

+ * **Transacciones (Spring Boot):** Este método está marcado con `@Transactional` de Spring, lo que significa + * que la operación de base de datos se ejecuta dentro de una transacción. Si ocurre alguna excepción durante el + * proceso (por ejemplo, si el usuario ya existe o alguna validación falla), la transacción será revertida + * automáticamente, asegurando que ningún cambio se persista en la base de datos. + *

+ * **Rollback:** Spring manejará el rollback de forma automática si se lanza una excepción no verificada (como una + * `IllegalStateException` o `RuntimeException`). De este modo, si algo falla, se asegura la consistencia de la base de datos. + *

* @param userDTO el objeto {@link UserDTO} que contiene los datos del nuevo usuario. * @return un objeto {@link UserDTO} que representa al usuario creado. * @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. * @throws ValidationException si las validaciones de los campos del usuario no son exitosas. * * @see UserDTO * @see ValidationException + * @see Transactional */ @Override + @Transactional @CachePut(value = "users", key = "#userDTO.id") public UserDTO CreateUser(UserDTO userDTO) throws ValidationException, IllegalStateException { return Optional.of(userDTO) @@ -325,11 +335,19 @@ public List getAllRoles() throws Exception { /** * Actualiza la información de un usuario en la base de datos. - * * + *

* Este método permite actualizar los detalles de un usuario existente en la base de datos. Primero, * se busca el usuario por su ID. Si el usuario existe, se actualizan los campos del usuario con los * 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. + *

+ * **Transacciones (Spring Boot):** Al igual que el método `CreateUser`, este método está marcado con la anotación + * `@Transactional`, lo que significa que la operación de actualización se ejecutará dentro de una transacción de Spring. + * Si ocurre algún error, la transacción se revertirá, asegurando que los cambios no se apliquen si algo falla en el proceso. + *

+ * **Rollback:** Las excepciones que extienden `RuntimeException` causarán un rollback automático, mientras que + * las excepciones comprobadas, como `ValidationException`, no harán que la transacción se revierta a menos que se + * indique explícitamente lo contrario. * * @param id el identificador del usuario a actualizar. * @param updatedUser el objeto {@link UserDTO} que contiene los nuevos valores para el usuario. @@ -338,8 +356,10 @@ public List getAllRoles() throws Exception { * @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 + * @see Transactional */ @Override + @Transactional @CachePut(value = "users", key = "'id:'+#id") public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { UserDTO existingUser = getUserById(id); @@ -373,17 +393,26 @@ public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception { /** * Elimina un usuario de la base de datos. - * * + *

* Este método permite eliminar un usuario de la base de datos utilizando su ID. Primero, se verifica * si el usuario existe. Si el usuario existe, se elimina de la base de datos. Si no se encuentra * el usuario con el ID proporcionado, se lanza una excepción indicando que el usuario no existe. + *

+ * **Transacciones (Spring Boot):** Este método también está marcado con `@Transactional`, lo que garantiza + * que si ocurre algún error durante la eliminación (por ejemplo, si el usuario no existe o si hay un fallo + * en la base de datos), la transacción será revertida y no se eliminará el usuario. + *

+ * **Rollback:** Al igual que los otros métodos, si ocurre una excepción de tipo `RuntimeException`, Spring + * realizará un rollback automáticamente para mantener la integridad de los datos. * * @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 + * @see Transactional */ @Override + @Transactional @CacheEvict(value = "users", key = "'id_'+#id") public String DeleteUser(String id) throws Exception { return Optional.of(getUserById(id)) diff --git a/src/main/java/smartpot/com/api/Users/Service/SUserI.java b/src/main/java/smartpot/com/api/Users/Service/SUserI.java index a5c30ac..42ce50d 100644 --- a/src/main/java/smartpot/com/api/Users/Service/SUserI.java +++ b/src/main/java/smartpot/com/api/Users/Service/SUserI.java @@ -20,7 +20,7 @@ public interface SUserI extends UserDetailsService { * * @param userDTO el objeto que contiene los datos del nuevo usuario a crear. * @return el objeto {@link UserDTO} del usuario creado. - * @throws Exception si el usuario ya existe o si ocurre un error durante la creación. + * @throws ValidationException,IllegalStateException si el usuario ya existe o si ocurre un error durante la creación. */ UserDTO CreateUser(UserDTO userDTO) throws ValidationException, IllegalStateException; From 691c8355a98be0279c20e33a4ed7e4a3fd64195f Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Fri, 31 Jan 2025 22:34:31 -0500 Subject: [PATCH 4/5] Implementation of transactional annotation --- src/main/java/smartpot/com/api/Commands/Service/SCommand.java | 4 ++++ src/main/java/smartpot/com/api/Crops/Service/SCrop.java | 4 ++++ 2 files changed, 8 insertions(+) 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 afd6730..56fcaab 100644 --- a/src/main/java/smartpot/com/api/Commands/Service/SCommand.java +++ b/src/main/java/smartpot/com/api/Commands/Service/SCommand.java @@ -8,6 +8,7 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import smartpot.com.api.Commands.Mapper.MCommand; import smartpot.com.api.Commands.Model.DTO.CommandDTO; import smartpot.com.api.Commands.Repository.RCommand; @@ -141,6 +142,7 @@ public CommandDTO getCommandById(String id) throws Exception { * @throws IllegalStateException if a command with the same details already exists */ @Override + @Transactional public CommandDTO createCommand(CommandDTO commandDTO) throws IllegalStateException { return Optional.of(commandDTO) .map(dto -> { @@ -199,6 +201,7 @@ public CommandDTO executeCommand(String id, String response) throws Exception { * @throws Exception if the command cannot be found or updated */ @Override + @Transactional @CachePut(value = "commands", key = "'id_'+#id") public CommandDTO updateCommand(String id, CommandDTO updateCommand) throws Exception { CommandDTO existingCommand = getCommandById(id); @@ -227,6 +230,7 @@ public CommandDTO updateCommand(String id, CommandDTO updateCommand) throws Exce * @throws Exception if the command cannot be found */ @Override + @Transactional @CacheEvict(value = "commands", key = "'id_'+#id") public String deleteCommand(String id) throws Exception { return Optional.of(getCommandById(id)) diff --git a/src/main/java/smartpot/com/api/Crops/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Service/SCrop.java index ff6dbc5..f6ae2da 100644 --- a/src/main/java/smartpot/com/api/Crops/Service/SCrop.java +++ b/src/main/java/smartpot/com/api/Crops/Service/SCrop.java @@ -10,6 +10,7 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import smartpot.com.api.Crops.Mapper.MCrop; import smartpot.com.api.Crops.Model.DTO.CropDTO; import smartpot.com.api.Crops.Model.Entity.CropStatus; @@ -85,6 +86,7 @@ public SCrop(RCrop repositoryCrop, SUserI serviceUser, MCrop mapperCrop, VCropI * @see MCrop */ @Override + @Transactional @CachePut(value = "crops", key = "#cropDTO.id") public CropDTO createCrop(CropDTO cropDTO) throws Exception { return Optional.of(cropDTO) @@ -369,6 +371,7 @@ public List getAllStatus() throws Exception { * @see MCrop */ @Override + @Transactional @CachePut(value = "crops", key = "'id_'+#id") public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { CropDTO existingCrop = getCropById(id); @@ -415,6 +418,7 @@ public CropDTO updatedCrop(String id, CropDTO updateCrop) throws Exception { * @see RCrop */ @Override + @Transactional @CacheEvict(value = "crops", key = "'id_'+#id") public String deleteCrop(String id) throws Exception { return Optional.of(getCropById(id)) From fd57284f4774a9a63abf0fce813dba9dbb7b5437 Mon Sep 17 00:00:00 2001 From: SebastianLopezO Date: Fri, 31 Jan 2025 22:37:23 -0500 Subject: [PATCH 5/5] Removing data initialization component --- .../smartpot/com/api/DataInitializer.java | 59 ------------------- src/main/resources/import.json | 52 ---------------- src/test/resources/application.properties | 44 -------------- src/test/resources/application.yml | 54 +++++++++++++++++ 4 files changed, 54 insertions(+), 155 deletions(-) delete mode 100644 src/main/java/smartpot/com/api/DataInitializer.java delete mode 100644 src/main/resources/import.json delete mode 100644 src/test/resources/application.properties create mode 100644 src/test/resources/application.yml diff --git a/src/main/java/smartpot/com/api/DataInitializer.java b/src/main/java/smartpot/com/api/DataInitializer.java deleted file mode 100644 index 1fe1f33..0000000 --- a/src/main/java/smartpot/com/api/DataInitializer.java +++ /dev/null @@ -1,59 +0,0 @@ -package smartpot.com.api; - -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -@Component -public class DataInitializer implements CommandLineRunner { - @Override - public void run(String... args) throws Exception { - - } - - /* @Autowired - private RUser userRepository; - - @Autowired - private RSession sessionRepository; - - @Autowired - private RCrop cropRepository; - - @Autowired - private RHistory historyRepository; - - @Override - public void run(String... args) throws Exception { - /* - ObjectMapper mapper = new ObjectMapper(); - InputStream inputStream = TypeReference.class.getResourceAsStream("/import.json"); - - Map data = mapper.readValue(inputStream, new TypeReference>() { - }); - - if (data.containsKey("usuarios")) { - List usuarios = mapper.convertValue(data.get("usuarios"), new TypeReference>() { - }); - userRepository.saveAll(usuarios); - } - - *//* if (data.containsKey("sesiones")) { - List sessions = mapper.convertValue(data.get("sesiones"), new TypeReference>() { - }); - sessionRepository.saveAll(sessions); - }*//* - - if (data.containsKey("cultivos")) { - List cultivos = mapper.convertValue(data.get("cultivos"), new TypeReference>() { - }); - cropRepository.saveAll(cultivos); - } - - if (data.containsKey("registros")) { - List historiales = mapper.convertValue(data.get("registros"), new TypeReference>() { - }); - historyRepository.saveAll(historiales); - } - - */ -} \ No newline at end of file diff --git a/src/main/resources/import.json b/src/main/resources/import.json deleted file mode 100644 index 63ff219..0000000 --- a/src/main/resources/import.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "usuarios": [ - { - "name": "Juan", - "lastname": "Pérez", - "email": "juan.perez@example.com", - "password": "Contraseña1", - "role": "USER" - }, - { - "name": "María", - "lastname": "González", - "email": "maria.gonzalez@example.com", - "password": "Contraseña2", - "role": "ADMIN" - } - ], - "sesiones": [ - { - "registration": "2023-10-01T10:00:00Z", - "user": "user_id_1" - } - ], - "cultivos": [ - { - "status": "Activo", - "type": "Hortaliza", - "user": "user_id_1" - } - ], - "registros": [ - { - "date": "2023-10-02T00:00:00Z", - "measures": { - "atmosphere": 20.5, - "brightness": 300, - "temperature": 22.0, - "ph": 6.5, - "tds": 500, - "humidity": 60 - }, - "cultivation": "cultivo_id_1" - } - ], - "commandos": { - "commandType": "ACTIVATE_WATER_PUMP", - "status": "PENDING", - "dateCreated": "2023-10-03T15:30:00Z", - "response": null, - "crop": "cultivo_id_1" - } -} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties deleted file mode 100644 index 8f3e38c..0000000 --- a/src/test/resources/application.properties +++ /dev/null @@ -1,44 +0,0 @@ -# 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 - diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..79addf5 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,54 @@ +application: + author: SmartPot Developers + description: Entorno de Prueba para API de SmartPot + security: + jwt: + expiration: 4102444800 + secret-key: mySuperSecretKey + public: + routes: /** + title: SmartPot-API-Test + version: 1.0.0 +http: + header: + cors: + allowedOrigins: '*' +logging: + level: + com: + mongodb: DEBUG + io: + swagger: DEBUG + org: + springdoc: DEBUG + springframework: DEBUG + springframework.data.mongodb: DEBUG + springframework.web.filter: DEBUG + root: DEBUG + smartpot: + com: + api: + Security: + jwt: DEBUG +server: + port: 8091 + tomcat: + connection-timeout: 100000000 +spring: + application: + name: SmartPot-API-Test + data: + mongodb: + uri: mongo://localhost:27017/smartpot +springdoc: + api-docs: + enabled: true + swagger-ui: + default-model-expand-depth: 1 + default-model-rendering: example + display-operation-id: false + display-request-duration: true + doc-expansion: list + enabled: true + path: / + url: /v3/api-docs