diff --git a/.env.example b/.env.example
index d300f5d..4c5e64f 100644
--- a/.env.example
+++ b/.env.example
@@ -1,28 +1,69 @@
-# Configuración APP
-APP_NAME=app
-PORT=port
-TITLE=title
-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 of the API
+VERSION= # Version of your API
+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 (mongodb+srv or mongodb)
+DATA_SOURCE_USERNAME= # MongoDB username
+DATA_SOURCE_PASSWORD= # MongoDB password
+DATA_SOURCE_DOMAIN= # MongoDB server domain
+DATA_SOURCE_DB= # Database name
+DATA_PARAMS= # Additional connection parameters
+
+# Redis Credentials
+# Settings for connecting to Redis (used for caching)
+CACHE_TYPE= # Cache type (redis)
+CACHE_HOST= # Redis host
+CACHE_PORT= # Redis port
+CACHE_DB= # Redis database ID (usually 0)
+CACHE_USERNAME= # Redis username
+CACHE_PASSWORD= # Redis password
+CACHE_TIMEOUT= # Timeout for cache operations (in ms)
+CACHE_LETTUCE_POOL_MAX_ACTIVE= # Max active connections in Redis pool
+CACHE_LETTUCE_POOL_MAX_WAIT= # Max wait time for Redis connections (in ms)
+CACHE_LETTUCE_POOL_MAX_IDLE= # Max idle connections in Redis pool
+CACHE_LETTUCE_POOL_MIN_IDLE= # Min idle connections in Redis pool
+
+# Cache Configuration
+# Settings for cache behavior
+CACHE_TIME_TO_LIVE= # Time to live for cache items (in ms)
+CACHE_NULL_VALUES= # Whether to store null values in cache (true/false)
+
+# Email Credentials
+# Settings for sending emails via SMTP
+MAIL_HOST= # SMTP server (e.g., Gmail)
+MAIL_PORT= # SMTP port (587 for TLS)
+MAIL_USERNAME= # SMTP login username
+MAIL_PASSWORD= # SMTP login password
+MAIL_PROPERTIES_SMTP_AUTH= # Enable SMTP authentication (true/false)
+MAIL_PROPERTIES_SMTP_STARTTLS_ENABLE= # 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= # Secret key for signing JWT tokens
+SECURITY_JWT_EXPIRATION= # JWT expiration time (in ms)
+SECURITY_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= # Max requests per client IP within the defined time window
+RATE_LIMITING_TIME_WINDOW= # Time window in milliseconds (1 minute)
+RATE_LIMITING_PUBLIC_ROUTES= # Public routes excluded from rate limiting
-# Https Headers
-HEADER_CORS_ALLOWED_ORIGINS=*
+# HTTPS Headers (CORS)
+# Settings for Cross-Origin Resource Sharing (CORS)
+HEADER_CORS_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= # 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= # Log level: INFO, DEBUG, or OFF
diff --git a/README.md b/README.md
index 54d8183..f49c082 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,9 @@
## Deployment
-[](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/deployment.yml)
+[](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/ci-pipeline.yml)
+
+[](https://github.com/SmartPotTech/SmartPot-API/actions/workflows/checkout.yml)
### 1. Compilación de la Aplicación
diff --git a/pom.xml b/pom.xml
index e0b6626..128b5d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,132 +2,189 @@
+
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
-
-
- org.springframework.boot
- spring-boot-starter-validation
+ spring-boot-starter-web
+
org.springframework.boot
- spring-boot-devtools
- runtime
- true
+ spring-boot-starter-validation
+
+
org.springframework.boot
- spring-boot-starter-test
- test
+ spring-boot-starter-data-mongodb
+
org.springframework.boot
- spring-boot-starter-web
-
-
- org.projectlombok
- lombok
- compile
-
-
- org.springdoc
- springdoc-openapi-starter-webmvc-ui
- 2.8.3
+ spring-boot-starter-data-redis
+ 3.4.1
+
- org.springframework.restdocs
- spring-restdocs-mockmvc
- test
+ redis.clients
+ jedis
+ 5.2.0
+
+
org.springframework.boot
- spring-boot-starter-security
+ spring-boot-starter-security
+
org.springframework.security
- spring-security-test
- test
+ spring-security-test
+ test
-
+
io.jsonwebtoken
jjwt
- 0.12.6
+ 0.12.6
io.jsonwebtoken
jjwt-impl
- 0.12.6
+ 0.12.6
io.jsonwebtoken
jjwt-jackson
- 0.12.6
+ 0.12.6
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.restdocs
+ spring-restdocs-mockmvc
+ test
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+
+ org.projectlombok
+ lombok
+ compile
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.8.3
+
+
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
+
+
@@ -136,21 +193,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
new file mode 100644
index 0000000..f8ef184
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Cache/RedisConfig.java
@@ -0,0 +1,82 @@
+package smartpot.com.api.Cache;
+
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.springframework.beans.factory.annotation.Value;
+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.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
+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("${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
+ public LettuceConnectionFactory redisConnectionFactory() {
+ RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
+ redisStandaloneConfiguration.setHostName(host);
+ redisStandaloneConfiguration.setPort(Integer.parseInt(port));
+ redisStandaloneConfiguration.setDatabase(Integer.parseInt(database));
+ redisStandaloneConfiguration.setUsername(username);
+ redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
+
+ 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 LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
+ }
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(factory);
+ return template;
+ }
+}
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..535b6de 100644
--- a/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java
+++ b/src/main/java/smartpot/com/api/Commands/Controller/CommandController.java
@@ -1,59 +1,110 @@
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.Entity.Crop;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
+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;
@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 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 createCommand(@PathVariable String cropId, @RequestBody Command newCommand) {
- Optional 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);
}
}
- @PutMapping("/{id}/ejecutar")
- public ResponseEntity executeCommand(@PathVariable String id) {
+ @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");
@@ -64,20 +115,38 @@ public ResponseEntity executeCommand(@PathVariable String id) {
} 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 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);
}
}
@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/SCommandI.java b/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java
deleted file mode 100644
index 6335d7c..0000000
--- a/src/main/java/smartpot/com/api/Commands/Model/DAO/Service/SCommandI.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package smartpot.com.api.Commands.Model.DAO.Service;
-
-import smartpot.com.api.Commands.Model.Entity.Command;
-
-import java.util.List;
-
-public interface SCommandI {
- List getAllCommands();
-
- Command getCommandById(String id);
-
- Command createCommand(Command newCommand);
-
- Command updateCommand(String id, Command upCommand);
-
- void deleteCommand(String id);
-}
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/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 57%
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 a98cb6f..444c679 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,19 +1,26 @@
-package smartpot.com.api.Commands.Model.DAO.Service;
+package smartpot.com.api.Commands.Service;
import lombok.Builder;
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.Model.DAO.Repository.RCommand;
+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.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;
+import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
+import java.util.Optional;
@Data
@Builder
@@ -22,11 +29,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
@@ -35,19 +44,34 @@ public List 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
- 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
- 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).",
@@ -111,8 +135,31 @@ public Command updateCommand(String id, Command upCommand) {
}
@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
new file mode 100644
index 0000000..5a54198
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Commands/Service/SCommandI.java
@@ -0,0 +1,20 @@
+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;
+
+public interface SCommandI {
+ List getAllCommands();
+
+ CommandDTO getCommandById(String id) throws Exception;
+
+ CommandDTO createCommand(CommandDTO newCommand);
+
+ Command updateCommand(String id, Command upCommand) throws Exception;
+
+ String deleteCommand(String id) throws Exception;
+
+ CommandDTO excuteCommand(String id, String reponse) throws Exception;
+}
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..2af2ca7 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;
@@ -10,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;
@@ -42,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.
* 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.
*
- * @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:
*
* El cultivo recién creado (código HTTP 201).
@@ -67,9 +68,10 @@ 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 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);
@@ -95,9 +97,9 @@ public ResponseEntity> createCrop(@RequestBody CropDTO newCropDto) {
@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",
+ @ApiResponse(description = "Cultivos encontrados",
responseCode = "200",
content = @Content(mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = UserDTO.class)))),
@@ -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) {
@@ -191,6 +193,44 @@ public ResponseEntity> getCropsByStatus(@PathVariable String status) {
}
}
+ /**
+ * 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.
@@ -221,7 +261,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) {
@@ -229,6 +269,44 @@ public ResponseEntity> getCropsByType(@PathVariable String type) {
}
}
+ /**
+ * 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.
@@ -260,7 +338,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 +379,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) {
@@ -315,8 +393,8 @@ public ResponseEntity> countCropsByUser(@PathVariable String id) {
* 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:
*
@@ -342,7 +420,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 +460,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
deleted file mode 100644
index 7f67882..0000000
--- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Repository/RCrop.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package smartpot.com.api.Crops.Model.DAO.Repository;
-
-import org.bson.types.ObjectId;
-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;
-
-@Repository
-public interface RCrop extends MongoRepository {
-
- @Query("{ '_id' : ?0 }")
- Optional findById(ObjectId id);
-
- @Query("{ 'type' : ?0 }")
- List findByType(String type);
-
- @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);
-
-
-}
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
deleted file mode 100644
index 94643c8..0000000
--- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCrop.java
+++ /dev/null
@@ -1,264 +0,0 @@
-package smartpot.com.api.Crops.Model.DAO.Service;
-
-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.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.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;
-
-
-/**
- * Servicio que gestiona las operaciones relacionadas con los cultivos.
- * Proporciona métodos para crear, leer, actualizar y eliminar cultivos,
- * asà como búsquedas especÃficas por diferentes criterios.
- */
-@Slf4j
-@Data
-@Builder
-@Service
-public class SCrop implements SCropI {
-
- private final RCrop repositoryCrop;
- private final SUserI serviceUser;
-
- @Autowired
- public SCrop(RCrop repositoryCrop, SUserI serviceUser) {
- this.repositoryCrop = repositoryCrop;
- this.serviceUser = serviceUser;
- }
-
- /**
- * 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.
- *
- * @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.
- */
- @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())
- ));
- }
-
- /**
- * Obtiene todos los cultivos almacenados en el sistema.
- *
- * @return Lista de todos los cultivos existentes
- */
- @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;
- }
-
- /**
- * Busca todos los cultivos asociados a un usuario especÃfico.
- *
- * @param id del Usuario propietario de los cultivos
- * @return Lista de cultivos pertenecientes al usuario
- */
- @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;
- }
-
- /**
- * Busca cultivos por su tipo .
- *
- * @param type Tipo del cultivo
- * @return Lista de cultivos que coinciden con el tipo especificado
- */
- @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);
- }
-
- /**
- * Cuenta el número total de cultivos que tiene un usuario.
- *
- * @param id del Usuario del que se quieren contar los cultivos
- * @return Número total de cultivos del usuario
- */
- @Override
- public long countCropsByUser(String id) throws Exception {
- System.out.println("//////////////////////////////////////////////" + getCropsByUser(id).size());
- return getCropsByUser(id).size();
-
- }
-
- /**
- * Busca cultivos por su estado actual.
- *
- * @param status Estado del cultivo a buscar
- * @return Lista de cultivos que se encuentran en el estado especificado
- */
- @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;
- }
-
- /**
- * Crea un cultivo en el sistema.
- *
- * @return Cultivo guardado
- */
- @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);
- }
-
- /**
- * Actualiza la información de un Crop existente.
- *
- * @param id El identificador del Crop a actualizar.
- * @return El Crop actualizado después de guardarlo en el servicio.
- */
- @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);
- }
-
- 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;
- }
-
-
- /**
- * Elimina un cultivo existente por su identificador.
- *
- * @param id Es el identificador del cultivo que se desea eliminar.
- */
- /* 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()));
- }
- }
-}
-
-
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
deleted file mode 100644
index 80440f7..0000000
--- a/src/main/java/smartpot/com/api/Crops/Model/DAO/Service/SCropI.java
+++ /dev/null
@@ -1,43 +0,0 @@
-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 getCropsByUser(String id) throws Exception;
-
- List getCropsByType(String type);
-
- long countCropsByUser(String id) throws Exception;
-
- List getCropsByStatus(String status);
-
- Crop createCrop(CropDTO newCropDto) throws Exception;
-
- Crop updatedCrop(String id, CropDTO cropDto) 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())
- ));
-
- repositoryCrop.deleteById(existingCrop.getId());
- }*/
- ResponseEntity deleteCrop(Crop existingCrop);
-}
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..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
@@ -1,11 +1,37 @@
package smartpot.com.api.Crops.Model.DTO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import java.io.Serializable;
+
+
+/**
+ * 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
-public class CropDTO {
+@AllArgsConstructor
+@RequiredArgsConstructor
+public class CropDTO implements Serializable {
+
+ @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")
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..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,13 +34,16 @@ 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;
- /*@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/Crops/Model/Entity/Status.java b/src/main/java/smartpot/com/api/Crops/Model/Entity/CropStatus.java
similarity index 83%
rename from src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java
rename to src/main/java/smartpot/com/api/Crops/Model/Entity/CropStatus.java
index f447c33..51f9f53 100644
--- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Status.java
+++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/CropStatus.java
@@ -1,6 +1,6 @@
package smartpot.com.api.Crops.Model.Entity;
-public enum Status {
+public enum CropStatus {
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/CropType.java
similarity index 52%
rename from src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java
rename to src/main/java/smartpot/com/api/Crops/Model/Entity/CropType.java
index 1e4d894..6cb8fb4 100644
--- a/src/main/java/smartpot/com/api/Crops/Model/Entity/Type.java
+++ b/src/main/java/smartpot/com/api/Crops/Model/Entity/CropType.java
@@ -1,5 +1,5 @@
package smartpot.com.api.Crops.Model.Entity;
-public enum Type {
- TOMATTO, LETTUCE
+public enum CropType {
+ TOMATO, LETTUCE;
}
diff --git a/src/main/java/smartpot/com/api/Crops/Repository/RCrop.java b/src/main/java/smartpot/com/api/Crops/Repository/RCrop.java
new file mode 100644
index 0000000..22e5abf
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Crops/Repository/RCrop.java
@@ -0,0 +1,68 @@
+package smartpot.com.api.Crops.Repository;
+
+import org.bson.types.ObjectId;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.mongodb.repository.Query;
+import org.springframework.stereotype.Repository;
+import smartpot.com.api.Crops.Model.Entity.Crop;
+
+import java.util.List;
+
+/**
+ * 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 {
+ /**
+ * 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);
+
+ /**
+ * 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/Service/SCrop.java b/src/main/java/smartpot/com/api/Crops/Service/SCrop.java
new file mode 100644
index 0000000..ff6dbc5
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Crops/Service/SCrop.java
@@ -0,0 +1,429 @@
+package smartpot.com.api.Crops.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.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.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.Service.SUserI;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+
+/**
+ * Servicio que gestiona las operaciones relacionadas con los cultivos.
+ * Proporciona métodos para crear, leer, actualizar y eliminar cultivos,
+ * asà como búsquedas especÃficas por diferentes criterios.
+ */
+@Slf4j
+@Data
+@Builder
+@Service
+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, MCrop mapperCrop, VCropI validatorCrop) {
+ this.repositoryCrop = repositoryCrop;
+ this.serviceUser = serviceUser;
+ this.mapperCrop = mapperCrop;
+ this.validatorCrop = validatorCrop;
+ }
+
+ /**
+ * Crea un nuevo cultivo en la base de datos a partir de un objeto {@link CropDTO}.
+ *
+ * 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
+ @CachePut(value = "crops", key = "#cropDTO.id")
+ public CropDTO createCrop(CropDTO cropDTO) throws Exception {
+ return Optional.of(cropDTO)
+ .map(ValidCropDTO -> {
+ validatorCrop.validateType(ValidCropDTO.getType());
+ try {
+ serviceUser.getUserById(ValidCropDTO.getUser());
+ } catch (Exception e) {
+ throw new ValidationException(e.getMessage() + ", asocia el cultivo a un usuario existente");
+ }
+
+ 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 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 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
+ @Cacheable(value = "crops", key = "'all_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"));
+ }
+
+ /**
+ * 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 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
+ @Cacheable(value = "crops", key = "'id_'+#id")
+ 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"));
+ }
+
+ /**
+ * 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 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
+ @Cacheable(value = "crops", key = "'user_'+#id")
+ 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 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
+ @Cacheable(value = "crops", key = "'count_user_'+#id")
+ 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.
+ *
+ * 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}.
+ * @see CropDTO
+ * @see VCropI
+ * @see RCrop
+ * @see MCrop
+ * @see SCrop
+ */
+ @Override
+ @Cacheable(value = "crops", key = "'type_'+#type")
+ 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"));
+ }
+
+ /**
+ * Obtiene una lista de todos los tipos de cultivo registrados en el sistema.
+ *
+ * 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.
+ *
+ * @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 CropType
+ */
+ @Override
+ @Cacheable(value = "crops", key = "'all_types'")
+ public List getAllTypes() throws Exception {
+ 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"));
+ }
+
+
+ /**
+ * 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}.
+ * @see CropDTO
+ * @see VCropI
+ * @see RCrop
+ * @see MCrop
+ */
+ @Override
+ @Cacheable(value = "crops", key = "'status_'+#status")
+ 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"));
+ }
+
+ /**
+ * 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 CropStatus
+ */
+ @Override
+ @Cacheable(value = "crops", key = "'all_status'")
+ public List getAllStatus() throws Exception {
+ 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"));
+ }
+
+ /**
+ * Actualiza un cultivo existente en la base de datos con los nuevos datos proporcionados en un objeto {@link CropDTO}.
+ *
+ * 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
+ @CachePut(value = "crops", key = "'id_'+#id")
+ 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());
+ try {
+ serviceUser.getUserById(existingCrop.getId());
+ } catch (Exception e) {
+ throw new ValidationException(e.getMessage() + ", asocia el cultivo a un usuario existente");
+ }
+ if (!validatorCrop.isValid()) {
+ throw new ValidationException(validatorCrop.getErrors().toString());
+ }
+
+ 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 de la base de datos según el ID proporcionado.
+ *
+ * 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
+ */
+ @Override
+ @CacheEvict(value = "crops", key = "'id_'+#id")
+ 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/Service/SCropI.java b/src/main/java/smartpot/com/api/Crops/Service/SCropI.java
new file mode 100644
index 0000000..f5d4b51
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Crops/Service/SCropI.java
@@ -0,0 +1,31 @@
+package smartpot.com.api.Crops.Service;
+
+import smartpot.com.api.Crops.Model.DTO.CropDTO;
+
+import java.util.List;
+
+public interface SCropI {
+
+ List getAllCrops() throws Exception;
+
+ CropDTO getCropById(String id) throws Exception;
+
+ List getCropsByUser(String id) throws Exception;
+
+ long countCropsByUser(String id) throws Exception;
+
+ List getCropsByType(String type) 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/Validator/VCrop.java b/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java
new file mode 100644
index 0000000..67e2c31
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Crops/Validator/VCrop.java
@@ -0,0 +1,108 @@
+package smartpot.com.api.Crops.Validator;
+
+import org.bson.types.ObjectId;
+import org.springframework.stereotype.Component;
+import smartpot.com.api.Crops.Model.Entity.CropStatus;
+import smartpot.com.api.Crops.Model.Entity.CropType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@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) {
+ 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;
+ }
+ }
+
+ /**
+ * Obtiene la lista de errores encontrados durante la validación.
+ *
+ * @return Una lista de cadenas con los mensajes de error.
+ */
+ @Override
+ public List getErrors() {
+ List currentErrors = errors;
+ Reset();
+ return currentErrors;
+ }
+
+ /**
+ * 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) {
+ if (type == null || type.isEmpty()) {
+ errors.add("El tipo de cultivo no puede estar vacÃo");
+ valid = false;
+ }
+
+ Set 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));
+ valid = false;
+ }
+ }
+
+ @Override
+ 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 = 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));
+ valid = false;
+ }
+ }
+
+}
diff --git a/src/main/java/smartpot/com/api/Crops/Validator/VCropI.java b/src/main/java/smartpot/com/api/Crops/Validator/VCropI.java
new file mode 100644
index 0000000..697eee0
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Crops/Validator/VCropI.java
@@ -0,0 +1,17 @@
+package smartpot.com.api.Crops.Validator;
+
+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);
+}
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/Config/AsyncConfig.java b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java
new file mode 100644
index 0000000..33ec504
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Mail/Config/AsyncConfig.java
@@ -0,0 +1,91 @@
+package smartpot.com.api.Mail.Config;
+
+import jakarta.annotation.PreDestroy;
+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;
+
+/**
+ * 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;
+ }
+
+ /**
+ * 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();
+ }
+ }
+ }
+}
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..3531193
--- /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.DTO.EmailDTO;
+import smartpot.com.api.Mail.Service.EmailServiceI;
+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.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/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/DTO/EmailDTO.java b/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java
new file mode 100644
index 0000000..43c81e9
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Mail/Model/DTO/EmailDTO.java
@@ -0,0 +1,18 @@
+package smartpot.com.api.Mail.Model.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@RequiredArgsConstructor
+public class EmailDTO implements Serializable {
+ 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
new file mode 100644
index 0000000..eb7fc2b
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Mail/Model/Entity/EmailDetails.java
@@ -0,0 +1,33 @@
+package smartpot.com.api.Mail.Model.Entity;
+
+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
+@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/Mail/Repository/EmailRepository.java b/src/main/java/smartpot/com/api/Mail/Repository/EmailRepository.java
new file mode 100644
index 0000000..40cb3f3
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Mail/Repository/EmailRepository.java
@@ -0,0 +1,10 @@
+package smartpot.com.api.Mail.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/Service/EmailService.java b/src/main/java/smartpot/com/api/Mail/Service/EmailService.java
new file mode 100644
index 0000000..5b93d44
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Mail/Service/EmailService.java
@@ -0,0 +1,102 @@
+package smartpot.com.api.Mail.Service;
+
+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.cache.annotation.Cacheable;
+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.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;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class EmailService implements EmailServiceI {
+ 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) {
+ this.javaMailSender = javaMailSender;
+ this.emailRepository = emailRepository;
+ this.emailMapper = emailMapper;
+ }
+
+ @Override
+ @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);
+ emailRepository.save(details);
+ log.warn("Correo Enviado Exitosamente");
+ } catch (Exception e) {
+ log.error("Error al Enviar Correo " + e.getMessage());
+ }
+ }
+
+ @Override
+ @Async
+ public void 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);
+ 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
+ @Cacheable(value = "mails", key = "'all_mails'")
+ public List getAllMails() 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/Service/EmailServiceI.java b/src/main/java/smartpot/com/api/Mail/Service/EmailServiceI.java
new file mode 100644
index 0000000..d1cf5b2
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Mail/Service/EmailServiceI.java
@@ -0,0 +1,15 @@
+package smartpot.com.api.Mail.Service;
+
+import smartpot.com.api.Mail.Model.DTO.EmailDTO;
+import smartpot.com.api.Mail.Model.Entity.EmailDetails;
+
+import java.util.List;
+
+public interface EmailServiceI {
+
+ void sendSimpleMail(EmailDetails details);
+
+ void sendMailWithAttachment(EmailDetails details);
+
+ List getAllMails() throws Exception;
+}
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 bfd643b..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,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.Records.Service.SHistoryI;
+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/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/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 95%
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 c323f58..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.Entity.Crop;
+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;
@@ -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/Service/SHistoryI.java
similarity index 81%
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 2f20e0e..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;
@@ -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/Security/Filter/JwtAuthFilter.java b/src/main/java/smartpot/com/api/Security/Config/Filters/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/Config/Filters/JwtAuthFilter.java
index 1b5012d..9308d00 100644
--- a/src/main/java/smartpot/com/api/Security/Filter/JwtAuthFilter.java
+++ b/src/main/java/smartpot/com/api/Security/Config/Filters/JwtAuthFilter.java
@@ -1,4 +1,4 @@
-package smartpot.com.api.Security.Filter;
+package smartpot.com.api.Security.Config.Filters;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -11,8 +11,8 @@
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;
@@ -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/Config/Filters/RateLimitingFilter.java b/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java
new file mode 100644
index 0000000..fc38844
--- /dev/null
+++ b/src/main/java/smartpot/com/api/Security/Config/Filters/RateLimitingFilter.java
@@ -0,0 +1,79 @@
+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.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component
+public class RateLimitingFilter implements Filter {
+
+ private final Map requestsCount = new ConcurrentHashMap<>();
+
+ @Value("${rate.limiting.max-requests}")
+ private int MAX_REQUESTS;
+
+ @Value("${rate.limiting.time-window}")
+ private long TIME_WINDOW;
+
+ @Value("${rate.limiting.public-routes}")
+ private String publicRoutes;
+
+ private long windowStart = System.currentTimeMillis();
+
+ private List 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();
+
+ 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); // 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 4074b22..2f4d12b 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,9 @@
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.Config.headers.CorsConfig;
+import smartpot.com.api.Security.Config.Filters.JwtAuthFilter;
+import smartpot.com.api.Users.Service.SUser;
import java.util.Arrays;
import java.util.List;
@@ -51,7 +51,6 @@ public SecurityConfiguration(CorsConfig corsConfig, JwtAuthFilter jwtAuthFilter,
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSec) throws Exception {
- // Public Routes
List publicRoutesList;
if (publicRoutes.contains(",")) {
publicRoutesList = Arrays.asList(publicRoutes.split(","));
diff --git a/src/main/java/smartpot/com/api/Security/Headers/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/Headers/CorsConfig.java
rename to src/main/java/smartpot/com/api/Security/Config/headers/CorsConfig.java
index 3b23b90..9cc324c 100644
--- a/src/main/java/smartpot/com/api/Security/Headers/CorsConfig.java
+++ b/src/main/java/smartpot/com/api/Security/Config/headers/CorsConfig.java
@@ -1,4 +1,4 @@
-package smartpot.com.api.Security.Headers;
+package smartpot.com.api.Security.Config.headers;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
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..2720cff 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;
}
@@ -83,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/Service/JwtService.java b/src/main/java/smartpot/com/api/Security/Service/JwtService.java
index bfc6e74..b086689 100644
--- a/src/main/java/smartpot/com/api/Security/Service/JwtService.java
+++ b/src/main/java/smartpot/com/api/Security/Service/JwtService.java
@@ -6,15 +6,15 @@
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.Users.Model.DAO.Service.SUserI;
+import smartpot.com.api.Mail.Model.Entity.EmailDetails;
+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;
import java.util.HashMap;
@@ -24,29 +24,40 @@
@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;
-
/**
* Constructor que inyecta las dependencias del servicio.
*
* @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
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 + "'",
+ "Inicio de Sesion en Smartpot",
+ ""
+ ));
+ return validToken;
+ })
.orElseThrow(() -> new Exception("Credenciales Invalidas"));
}
@@ -63,7 +74,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 '.");
}
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/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/SmartPotApiApplication.java b/src/main/java/smartpot/com/api/SmartPotApiApplication.java
index f909b0a..29d4606 100644
--- a/src/main/java/smartpot/com/api/SmartPotApiApplication.java
+++ b/src/main/java/smartpot/com/api/SmartPotApiApplication.java
@@ -1,29 +1,38 @@
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.cache.annotation.EnableCaching;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@SpringBootApplication
@EnableMongoRepositories(basePackages = "smartpot.com.api")
+@EnableCaching
+@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/main/java/smartpot/com/api/Users/Controller/UserController.java b/src/main/java/smartpot/com/api/Users/Controller/UserController.java
index 074d2c7..c433630 100644
--- a/src/main/java/smartpot/com/api/Users/Controller/UserController.java
+++ b/src/main/java/smartpot/com/api/Users/Controller/UserController.java
@@ -7,22 +7,26 @@
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;
import org.springframework.http.ResponseEntity;
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.
* Este controlador proporciona una serie de métodos para gestionar usuarios en el sistema.
+ *
* @see SUserI
*/
@RestController
@RequestMapping("/Users")
+@CacheConfig(cacheNames = "users")
@Tag(name = "Usuarios", description = "Operaciones relacionadas con usuarios")
public class UserController {
@@ -75,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(
@@ -84,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);
}
}
@@ -108,7 +117,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 +135,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.
@@ -195,7 +205,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 +245,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 +285,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 +324,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) {
@@ -322,6 +332,44 @@ public ResponseEntity> getUsersByRole(@PathVariable @Parameter(description = "
}
}
+ /**
+ * 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.
@@ -355,8 +403,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 +442,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/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/java/smartpot/com/api/Users/Model/Entity/User.java b/src/main/java/smartpot/com/api/Users/Model/Entity/User.java
index f5c9211..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
@@ -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;
@@ -76,21 +74,5 @@ 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());
- }
+ private UserRole userRole;
}
diff --git a/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java b/src/main/java/smartpot/com/api/Users/Model/Entity/UserRole.java
similarity index 75%
rename from src/main/java/smartpot/com/api/Users/Model/Entity/Role.java
rename to src/main/java/smartpot/com/api/Users/Model/Entity/UserRole.java
index 2334658..d8fbfcf 100644
--- a/src/main/java/smartpot/com/api/Users/Model/Entity/Role.java
+++ b/src/main/java/smartpot/com/api/Users/Model/Entity/UserRole.java
@@ -1,5 +1,5 @@
package smartpot.com.api.Users.Model.Entity;
-public enum Role {
+public enum UserRole {
USER, ADMIN, SYSTEM
}
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 87%
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 5f4f102..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,5 +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;
@@ -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/Service/SUser.java
similarity index 82%
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 4b855b2..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;
@@ -6,15 +6,20 @@
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;
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.Validation.VUserI;
+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;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@@ -30,6 +35,7 @@
@Builder
@Service
public class SUser implements SUserI {
+
private final RUser repositoryUser;
private final MUser mapperUser;
private final VUserI validatorUser;
@@ -38,7 +44,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 +54,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}.
* *
@@ -79,25 +63,28 @@ public List getAllUsers() throws Exception {
* 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 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
*/
@Override
- public UserDTO CreateUser(UserDTO userDTO) throws Exception {
+ @CachePut(value = "users", key = "#userDTO.id")
+ public UserDTO CreateUser(UserDTO userDTO) throws ValidationException, IllegalStateException {
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();
@@ -111,7 +98,30 @@ 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"));
+ }
+
+
+ /**
+ * 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
+ @Cacheable(value = "users", key = "'all_users'")
+ 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"));
}
/**
@@ -124,18 +134,18 @@ public UserDTO CreateUser(UserDTO userDTO) 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
*/
@Override
+ @Cacheable(value = "users", key = "'id_'+#id")
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();
@@ -159,18 +169,18 @@ 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
*/
@Override
+ @Cacheable(value = "users", key = "'email_'+#email")
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();
@@ -194,18 +204,18 @@ 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
*/
@Override
+ @Cacheable(value = "users", key = "'name_'+#name")
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();
@@ -230,18 +240,18 @@ 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
*/
@Override
+ @Cacheable(value = "users", key = "'lastname_'+#lastname")
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();
@@ -266,18 +276,18 @@ 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
*/
@Override
+ @Cacheable(value = "users", key = "'role:'+#role")
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();
@@ -291,6 +301,28 @@ 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 UserRole
+ */
+ @Override
+ @Cacheable(value = "users", key = "'all_rols'")
+ public List getAllRoles() throws Exception {
+ 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"));
+ }
+
/**
* Actualiza la información de un usuario en la base de datos.
* *
@@ -299,16 +331,16 @@ public List getUsersByRole(String role) 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
*/
@Override
+ @CachePut(value = "users", key = "'id:'+#id")
public UserDTO UpdateUser(String id, UserDTO updatedUser) throws Exception {
UserDTO existingUser = getUserById(id);
return Optional.of(updatedUser)
@@ -349,10 +381,10 @@ 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
+ @CacheEvict(value = "users", key = "'id_'+#id")
public String DeleteUser(String id) throws Exception {
return Optional.of(getUserById(id))
.map(user -> {
@@ -373,7 +405,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/Service/SUserI.java
similarity index 87%
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 6c50ece..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,5 +1,6 @@
-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;
import smartpot.com.api.Users.Model.DTO.UserDTO;
@@ -14,14 +15,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.
*
@@ -29,7 +22,15 @@ 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.
+ *
+ * @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.
@@ -76,10 +77,18 @@ 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.
*
- * @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.
@@ -94,4 +103,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/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 81%
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 4c6202a..4b7c83a 100644
--- a/src/main/java/smartpot/com/api/Users/Validation/VUser.java
+++ b/src/main/java/smartpot/com/api/Users/Validator/VUser.java
@@ -1,14 +1,17 @@
-package smartpot.com.api.Users.Validation;
+package smartpot.com.api.Users.Validator;
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.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.Validation.UserRegex.*;
+import static smartpot.com.api.Users.Validator.UserRegex.*;
/**
* Clase de validación para los usuarios.
@@ -23,10 +26,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;
/**
@@ -45,7 +52,7 @@ public VUser() {
*/
@Override
public boolean isValid() {
- return !valid;
+ return valid;
}
/**
@@ -55,7 +62,9 @@ public boolean isValid() {
*/
@Override
public List getErrors() {
- return errors;
+ List currentErrors = errors;
+ Reset();
+ return currentErrors;
}
/**
@@ -163,32 +172,15 @@ 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 = 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));
+ valid = false;
}
- return roleNames;
}
}
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;
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 4cfd854..9eba63c 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -14,6 +14,28 @@ 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
@@ -25,6 +47,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}
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/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/Users/Controller/UserControllerTest.java b/src/test/java/smartpot/com/api/Users/Controller/UserControllerTest.java
new file mode 100644
index 0000000..c1afed7
--- /dev/null
+++ b/src/test/java/smartpot/com/api/Users/Controller/UserControllerTest.java
@@ -0,0 +1,54 @@
+package smartpot.com.api.Users.Controller;
+
+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 UserControllerTest {
+
+ @BeforeEach
+ void setUp() {
+ }
+
+ @AfterEach
+ void tearDown() {
+ }
+
+ @Test
+ void createUser() {
+ }
+
+ @Test
+ void getAllUsers() {
+ }
+
+ @Test
+ void getUserById() {
+ }
+
+ @Test
+ void getUsersByEmail() {
+ }
+
+ @Test
+ void getUsersByName() {
+ }
+
+ @Test
+ void getUsersByLastname() {
+ }
+
+ @Test
+ void getUsersByRole() {
+ }
+
+ @Test
+ void updateUser() {
+ }
+
+ @Test
+ void deleteUser() {
+ }
+}
\ 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
+