diff --git a/build.gradle b/build.gradle index 49884ef..bf0bc7d 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CategoryResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CategoryResource.java index e3d13bf..8c28e74 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CategoryResource.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CategoryResource.java @@ -1,7 +1,7 @@ package br.com.fiap.grupo30.fastfood.adapters.in.rest; import br.com.fiap.grupo30.fastfood.application.dto.CategoryDTO; -import br.com.fiap.grupo30.fastfood.application.services.CategoryService; +import br.com.fiap.grupo30.fastfood.application.useCases.CategoryUseCase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; @@ -16,11 +16,11 @@ @Tag(name = "Categories Resource", description = "RESTful API for managing categories.") public class CategoryResource { - private final CategoryService categoryService; + private final CategoryUseCase categoryUseCase; @Autowired - public CategoryResource(CategoryService categoryService) { - this.categoryService = categoryService; + public CategoryResource(CategoryUseCase categoryUseCase) { + this.categoryUseCase = categoryUseCase; } @GetMapping @@ -28,7 +28,7 @@ public CategoryResource(CategoryService categoryService) { summary = "Get all categories", description = "Retrieve a list of all registered categories") public ResponseEntity> findAll() { - List list = categoryService.findAll(); + List list = categoryUseCase.findProducts(); return ResponseEntity.ok().body(list); } } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CustomerResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CustomerResource.java index ca8635f..2f0dbca 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CustomerResource.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/CustomerResource.java @@ -1,9 +1,10 @@ package br.com.fiap.grupo30.fastfood.adapters.in.rest; import br.com.fiap.grupo30.fastfood.application.dto.CustomerDTO; -import br.com.fiap.grupo30.fastfood.application.services.CustomerService; +import br.com.fiap.grupo30.fastfood.application.useCases.CustomerUseCase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.net.URI; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -12,23 +13,23 @@ @RestController @RequestMapping(value = "/customers") -@Tag(name = "Customer Resource", description = "RESTful API for managing customers.") +@Tag(name = "Customers Resource", description = "RESTful API for managing customers.") public class CustomerResource { private static final String PATH_VARIABLE_ID = "/{id}"; - private final CustomerService customerService; + private final CustomerUseCase customerUseCase; @Autowired - public CustomerResource(CustomerService customerService) { - this.customerService = customerService; + public CustomerResource(CustomerUseCase customerUseCase) { + this.customerUseCase = customerUseCase; } @GetMapping @Operation(summary = "Get a customer", description = "Retrieve a registered customer by cpf") public ResponseEntity findCustomerByCpf( @RequestParam(value = "cpf", defaultValue = "0") String cpf) { - CustomerDTO dto = customerService.findCustomerByCpf(cpf); + CustomerDTO dto = customerUseCase.findCustomerByCpf(cpf); return ResponseEntity.ok().body(dto); } @@ -36,8 +37,8 @@ public ResponseEntity findCustomerByCpf( @Operation( summary = "Create a new customer", description = "Create a new customer and return the created customer's data") - public ResponseEntity insert(@RequestBody CustomerDTO dto) { - CustomerDTO dtoCreated = customerService.insert(dto); + public ResponseEntity createCustomer(@RequestBody @Valid CustomerDTO dto) { + CustomerDTO dtoCreated = customerUseCase.createCustomer(dto); URI uri = ServletUriComponentsBuilder.fromCurrentRequest() .path(PATH_VARIABLE_ID) diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/ProductResource.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/ProductResource.java index 46aecac..b17dac8 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/ProductResource.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/ProductResource.java @@ -1,9 +1,10 @@ package br.com.fiap.grupo30.fastfood.adapters.in.rest; import br.com.fiap.grupo30.fastfood.application.dto.ProductDTO; -import br.com.fiap.grupo30.fastfood.application.services.ProductService; +import br.com.fiap.grupo30.fastfood.application.useCases.ProductUseCase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.net.URI; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -18,11 +19,11 @@ public class ProductResource { private static final String PATH_VARIABLE_ID = "/{id}"; - private final ProductService productService; + private final ProductUseCase productUseCase; @Autowired - public ProductResource(ProductService productService) { - this.productService = productService; + public ProductResource(ProductUseCase productUseCase) { + this.productUseCase = productUseCase; } @GetMapping @@ -31,9 +32,9 @@ public ProductResource(ProductService productService) { description = "Retrieve a list of all registered products or by categoryId " + "via RequestParam. i.e., ?categoryId=1") - public ResponseEntity> findAll( + public ResponseEntity> findProductsByCategoryId( @RequestParam(value = "categoryId", defaultValue = "0") Long categoryId) { - List list = productService.findProductsByCategoryId(categoryId); + List list = productUseCase.findProductsByCategoryId(categoryId); return ResponseEntity.ok().body(list); } @@ -41,8 +42,8 @@ public ResponseEntity> findAll( @Operation( summary = "Get a product by ID", description = "Retrieve a specific product based on its ID") - public ResponseEntity findById(@PathVariable Long id) { - ProductDTO dto = productService.findById(id); + public ResponseEntity findProductById(@PathVariable Long id) { + ProductDTO dto = productUseCase.findProductById(id); return ResponseEntity.ok().body(dto); } @@ -50,8 +51,8 @@ public ResponseEntity findById(@PathVariable Long id) { @Operation( summary = "Create a new product", description = "Create a new product and return the created product's data") - public ResponseEntity insert(@RequestBody ProductDTO dto) { - ProductDTO dtoCreated = productService.insert(dto); + public ResponseEntity createProduct(@RequestBody @Valid ProductDTO dto) { + ProductDTO dtoCreated = productUseCase.createProduct(dto); URI uri = ServletUriComponentsBuilder.fromCurrentRequest() .path(PATH_VARIABLE_ID) @@ -64,8 +65,9 @@ public ResponseEntity insert(@RequestBody ProductDTO dto) { @Operation( summary = "Update a product", description = "Update the data of an existing product based on its ID") - public ResponseEntity update(@PathVariable Long id, @RequestBody ProductDTO dto) { - ProductDTO dtoUpdated = productService.update(id, dto); + public ResponseEntity updateProduct( + @PathVariable Long id, @RequestBody @Valid ProductDTO dto) { + ProductDTO dtoUpdated = productUseCase.updateProduct(id, dto); return ResponseEntity.ok().body(dtoUpdated); } @@ -73,8 +75,8 @@ public ResponseEntity update(@PathVariable Long id, @RequestBody Pro @Operation( summary = "Delete a product", description = "Delete an existing product based on its ID") - public ResponseEntity delete(@PathVariable Long id) { - productService.delete(id); + public ResponseEntity deleteProduct(@PathVariable Long id) { + productUseCase.deleteProduct(id); return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/FieldMessage.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/FieldMessage.java new file mode 100644 index 0000000..6c10673 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/FieldMessage.java @@ -0,0 +1,13 @@ +package br.com.fiap.grupo30.fastfood.adapters.in.rest.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FieldMessage { + private String fieldName; + private String message; +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/ResourceExceptionHandler.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/ResourceExceptionHandler.java index 14fd3ce..01e834e 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/ResourceExceptionHandler.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/ResourceExceptionHandler.java @@ -1,13 +1,12 @@ package br.com.fiap.grupo30.fastfood.adapters.in.rest.exceptions; -import br.com.fiap.grupo30.fastfood.application.services.exceptions.DatabaseException; -import br.com.fiap.grupo30.fastfood.application.services.exceptions.ResourceBadRequestException; -import br.com.fiap.grupo30.fastfood.application.services.exceptions.ResourceConflictException; -import br.com.fiap.grupo30.fastfood.application.services.exceptions.ResourceNotFoundException; +import br.com.fiap.grupo30.fastfood.application.services.exceptions.*; import jakarta.servlet.http.HttpServletRequest; import java.time.Instant; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -64,4 +63,22 @@ public ResponseEntity database(DatabaseException e, HttpServletRe err.setPath(request.getRequestURI()); return ResponseEntity.status(status).body(err); } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity validation( + MethodArgumentNotValidException e, HttpServletRequest request) { + HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY; + ValidationError err = new ValidationError(); + err.setTimestamp(Instant.now()); + err.setStatus(status.value()); + err.setError("Validation exception"); + err.setMessage(e.getMessage()); + err.setPath(request.getRequestURI()); + + for (FieldError f : e.getBindingResult().getFieldErrors()) { + err.addError(f.getField(), f.getDefaultMessage()); + } + + return ResponseEntity.status(status).body(err); + } } diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/ValidationError.java b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/ValidationError.java new file mode 100644 index 0000000..8325f48 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/adapters/in/rest/exceptions/ValidationError.java @@ -0,0 +1,14 @@ +package br.com.fiap.grupo30.fastfood.adapters.in.rest.exceptions; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; + +@Getter +public class ValidationError extends StandardError { + private final List errors = new ArrayList<>(); + + public void addError(String fieldName, String message) { + errors.add(new FieldMessage(fieldName, message)); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CategoryDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CategoryDTO.java index 8e1ca6a..3c933ef 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CategoryDTO.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CategoryDTO.java @@ -1,6 +1,7 @@ package br.com.fiap.grupo30.fastfood.application.dto; import br.com.fiap.grupo30.fastfood.domain.Category; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -11,6 +12,8 @@ public class CategoryDTO { private Long id; + + @NotBlank(message = "Campo requirido") private String name; public CategoryDTO(Category entity) { diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CustomerDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CustomerDTO.java index 10ee3e9..27c1f89 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CustomerDTO.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/CustomerDTO.java @@ -1,6 +1,8 @@ package br.com.fiap.grupo30.fastfood.application.dto; import br.com.fiap.grupo30.fastfood.domain.Customer; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -11,8 +13,14 @@ public class CustomerDTO { private Long id; + + @NotBlank(message = "Campo obrigatório") private String name; + + @NotBlank(message = "Campo obrigatório") private String cpf; + + @Email(message = "Favor entrar um email válido") private String email; public CustomerDTO(Customer entity) { diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/ProductDTO.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/ProductDTO.java index 15bc000..c1d954f 100644 --- a/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/ProductDTO.java +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/dto/ProductDTO.java @@ -1,6 +1,8 @@ package br.com.fiap.grupo30.fastfood.application.dto; import br.com.fiap.grupo30.fastfood.domain.Product; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -11,10 +13,18 @@ public class ProductDTO { private Long id; + + @NotBlank(message = "Campo requirido") private String name; + + @NotBlank(message = "Campo requirido") private String description; - private Double price; + + @Positive(message = "Preço deve ser um valor positivo") private Double price; + + @NotBlank(message = "Campo requirido") private String imgUrl; + private CategoryDTO category; public ProductDTO(Product entity) { diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/CategoryUseCase.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/CategoryUseCase.java new file mode 100644 index 0000000..456cc7f --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/CategoryUseCase.java @@ -0,0 +1,9 @@ +package br.com.fiap.grupo30.fastfood.application.useCases; + +import br.com.fiap.grupo30.fastfood.application.dto.CategoryDTO; +import java.util.List; + +public interface CategoryUseCase { + + List findProducts(); +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/CustomerUseCase.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/CustomerUseCase.java new file mode 100644 index 0000000..0d6d760 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/CustomerUseCase.java @@ -0,0 +1,10 @@ +package br.com.fiap.grupo30.fastfood.application.useCases; + +import br.com.fiap.grupo30.fastfood.application.dto.CustomerDTO; + +public interface CustomerUseCase { + + CustomerDTO findCustomerByCpf(String cpf); + + CustomerDTO createCustomer(CustomerDTO dto); +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/ProductUseCase.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/ProductUseCase.java new file mode 100644 index 0000000..d77a12b --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/ProductUseCase.java @@ -0,0 +1,17 @@ +package br.com.fiap.grupo30.fastfood.application.useCases; + +import br.com.fiap.grupo30.fastfood.application.dto.ProductDTO; +import java.util.List; + +public interface ProductUseCase { + + List findProductsByCategoryId(Long categoryId); + + ProductDTO findProductById(Long id); + + ProductDTO createProduct(ProductDTO productDTO); + + ProductDTO updateProduct(Long id, ProductDTO dto); + + void deleteProduct(Long id); +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/CategoryUseCaseImpl.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/CategoryUseCaseImpl.java new file mode 100644 index 0000000..47ef7c9 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/CategoryUseCaseImpl.java @@ -0,0 +1,22 @@ +package br.com.fiap.grupo30.fastfood.application.useCases.impl; + +import br.com.fiap.grupo30.fastfood.application.dto.CategoryDTO; +import br.com.fiap.grupo30.fastfood.application.services.CategoryService; +import br.com.fiap.grupo30.fastfood.application.useCases.CategoryUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class CategoryUseCaseImpl implements CategoryUseCase { + + private final CategoryService categoryService; + + public CategoryUseCaseImpl(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @Override + public List findProducts() { + return categoryService.findAll(); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/CustomerUseCaseImpl.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/CustomerUseCaseImpl.java new file mode 100644 index 0000000..516cef9 --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/CustomerUseCaseImpl.java @@ -0,0 +1,26 @@ +package br.com.fiap.grupo30.fastfood.application.useCases.impl; + +import br.com.fiap.grupo30.fastfood.application.dto.CustomerDTO; +import br.com.fiap.grupo30.fastfood.application.services.CustomerService; +import br.com.fiap.grupo30.fastfood.application.useCases.CustomerUseCase; +import org.springframework.stereotype.Service; + +@Service +public class CustomerUseCaseImpl implements CustomerUseCase { + + private final CustomerService customerService; + + public CustomerUseCaseImpl(CustomerService customerService) { + this.customerService = customerService; + } + + @Override + public CustomerDTO findCustomerByCpf(String cpf) { + return customerService.findCustomerByCpf(cpf); + } + + @Override + public CustomerDTO createCustomer(CustomerDTO dto) { + return customerService.insert(dto); + } +} diff --git a/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/ProductUseCaseImpl.java b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/ProductUseCaseImpl.java new file mode 100644 index 0000000..3d96bdb --- /dev/null +++ b/src/main/java/br/com/fiap/grupo30/fastfood/application/useCases/impl/ProductUseCaseImpl.java @@ -0,0 +1,42 @@ +package br.com.fiap.grupo30.fastfood.application.useCases.impl; + +import br.com.fiap.grupo30.fastfood.application.dto.ProductDTO; +import br.com.fiap.grupo30.fastfood.application.services.ProductService; +import br.com.fiap.grupo30.fastfood.application.useCases.ProductUseCase; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class ProductUseCaseImpl implements ProductUseCase { + + private final ProductService productService; + + public ProductUseCaseImpl(ProductService productService) { + this.productService = productService; + } + + @Override + public List findProductsByCategoryId(Long categoryId) { + return productService.findProductsByCategoryId(categoryId); + } + + @Override + public ProductDTO findProductById(Long id) { + return productService.findById(id); + } + + @Override + public ProductDTO createProduct(ProductDTO productDTO) { + return productService.insert(productDTO); + } + + @Override + public ProductDTO updateProduct(Long id, ProductDTO dto) { + return productService.update(id, dto); + } + + @Override + public void deleteProduct(Long id) { + productService.delete(id); + } +}