Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
Expand All @@ -56,6 +66,13 @@
<version>1.18.30</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/orderflow/ecommerce/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.orderflow.ecommerce.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // Desabilita CSRF (comum em APIs REST)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // API sem estado
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll() // Por enquanto, libera tudo para você não se travar
);

return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import com.orderflow.ecommerce.entities.Category;
import com.orderflow.ecommerce.repositories.CategoryRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.NoSuchElementException;

@RestController
@RequestMapping(value = "/categories")
Expand All @@ -22,15 +24,13 @@ public ResponseEntity<List<Category>> findAll() {

@GetMapping(value = "/{id}")
public ResponseEntity<Category> findById(@PathVariable Long id) {


return repository.findById(id)
.map(obj -> ResponseEntity.ok().body(obj))
.orElse(ResponseEntity.notFound().build());
Category obj = repository.findById(id)
.orElseThrow(() -> new NoSuchElementException("Categoria não encontrada com o ID: " + id));
return ResponseEntity.ok().body(obj);
}

@PostMapping
public ResponseEntity<Category> insert(@RequestBody Category obj) {
public ResponseEntity<Category> insert(@Valid @RequestBody Category obj) {
return ResponseEntity.ok().body(repository.save(obj));
}

Expand All @@ -41,13 +41,11 @@ public ResponseEntity<Void> delete(@PathVariable Long id) {
}

@PutMapping(value = "/{id}")
public ResponseEntity<Category> update(@PathVariable Long id, @RequestBody Category obj) {
return repository.findById(id)
.map(entity -> {
entity.setName(obj.getName());
Category updated = repository.save(entity);
return ResponseEntity.ok().body(updated);
})
.orElse(ResponseEntity.notFound().build());
public ResponseEntity<Category> update(@PathVariable Long id, @Valid @RequestBody Category obj) {
Category entity = repository.findById(id)
.orElseThrow(() -> new NoSuchElementException("Categoria não encontrada para atualizar"));

entity.setName(obj.getName());
return ResponseEntity.ok().body(repository.save(entity));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import com.orderflow.ecommerce.entities.Product;
import com.orderflow.ecommerce.repositories.ProductRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.NoSuchElementException;

@RestController
@RequestMapping(value = "/products")
Expand All @@ -22,13 +24,13 @@ public ResponseEntity<List<Product>> findAll() {

@GetMapping(value = "/{id}")
public ResponseEntity<Product> findById(@PathVariable Long id) {
return repository.findById(id)
.map(obj -> ResponseEntity.ok().body(obj))
.orElse(ResponseEntity.notFound().build());
Product obj = repository.findById(id)
.orElseThrow(() -> new NoSuchElementException("Produto não encontrado"));
return ResponseEntity.ok().body(obj);
}

@PostMapping
public ResponseEntity<Product> insert(@RequestBody Product obj) {
public ResponseEntity<Product> insert(@Valid @RequestBody Product obj) {
return ResponseEntity.ok().body(repository.save(obj));
}

Expand All @@ -39,15 +41,16 @@ public ResponseEntity<Void> delete(@PathVariable Long id) {
}

@PutMapping(value = "/{id}")
public ResponseEntity<Product> update(@PathVariable Long id, @RequestBody Product obj) {
Product entity = repository.findById(id).get();
public ResponseEntity<Product> update(@PathVariable Long id, @Valid @RequestBody Product obj) {
Product entity = repository.findById(id)
.orElseThrow(() -> new NoSuchElementException("Produto não encontrado para atualizar"));

entity.setName(obj.getName());
entity.setDescription(obj.getDescription());
entity.setPrice(obj.getPrice());
entity.setStockQuantity(obj.getStockQuantity());
entity.setCategory(obj.getCategory());
System.out.println("aavavvvv");
return ResponseEntity.ok().body(repository.save(entity));

return ResponseEntity.ok().body(repository.save(entity));
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/orderflow/ecommerce/dtos/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.orderflow.ecommerce.dtos;

import java.time.Instant;

public record ErrorResponse(
Instant timestamp,
Integer status,
String message,
String path
) {
}
4 changes: 4 additions & 0 deletions src/main/java/com/orderflow/ecommerce/entities/Category.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.orderflow.ecommerce.entities;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.*;

@Entity
Expand All @@ -16,5 +18,7 @@ public class Category {
private Long id;

@Column(nullable = false, unique = true)
@NotBlank(message = "O nome da categoria é obrigatório")
@Pattern(regexp = "^[a-zA-ZÀ-ÿ ]+$", message = "O nome deve conter apenas letras")
private String name;
}
6 changes: 6 additions & 0 deletions src/main/java/com/orderflow/ecommerce/entities/Product.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.orderflow.ecommerce.entities;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.*;
import java.math.BigDecimal;

Expand All @@ -16,12 +19,15 @@ public class Product {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotBlank(message = "O nome do produto é obrigatório")
@Pattern(regexp = "^[a-zA-ZÀ-ÿ ]+$", message = "O nome deve conter apenas letras")
@Column(nullable = false)
private String name;

@Column(columnDefinition = "TEXT")
private String description;

@NotNull(message = "O preço é obrigatório")
@Column(nullable = false)
private BigDecimal price;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.orderflow.ecommerce.exceptions;

import org.springframework.security.access.AccessDeniedException;
import com.orderflow.ecommerce.dtos.ErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.Instant;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex, HttpServletRequest request) {
String errors = ex.getBindingResult().getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));

ErrorResponse err = new ErrorResponse(Instant.now(), HttpStatus.BAD_REQUEST.value(), errors, request.getRequestURI());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(err);
}

@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<ErrorResponse> handleNotFound(NoSuchElementException ex, HttpServletRequest request) {
ErrorResponse err = new ErrorResponse(Instant.now(), HttpStatus.NOT_FOUND.value(), ex.getMessage(), request.getRequestURI());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(err);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex, HttpServletRequest request) {
ErrorResponse err = new ErrorResponse(Instant.now(), HttpStatus.INTERNAL_SERVER_ERROR.value(), "Erro interno no servidor", request.getRequestURI());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(err);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpServletRequest request) {
String message = "Erro na estrutura do JSON: Verifique se você enviou números onde deveria ser texto, ou vice-versa.";

if (ex.getMessage().contains("Cannot deserialize")) {
message = "Erro de tipo de dado: Você tentou inserir um valor incompatível com o campo (ex: letras em números ou formato inválido).";
}

ErrorResponse err = new ErrorResponse(Instant.now(), HttpStatus.BAD_REQUEST.value(), message, request.getRequestURI());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(err);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDenied(AccessDeniedException ex, HttpServletRequest request) {
ErrorResponse err = new ErrorResponse(
Instant.now(),
HttpStatus.FORBIDDEN.value(),
"Você não tem permissão para acessar este recurso.",
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(err);
}
}
39 changes: 39 additions & 0 deletions src/test/java/com/orderflow/ecommerce/entities/CategoryTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.orderflow.ecommerce.entities;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class CategoryTest {

@Test
void shouldCreateCategoryWithCorrectName() {
Category cat = new Category(null, "Eletrônicos");

assertNotNull(cat);
assertEquals("Eletrônicos", cat.getName());
}

@Test
void shouldUpdateCategoryName() {
Category cat = new Category(1L, "Livros");
cat.setName("Games");

assertEquals("Games", cat.getName());
}
@Test
void shouldCreateCategoryWithNullId() {
Category cat = new Category();
cat.setName("Moda");

assertNotNull(cat);
assertEquals("Moda", cat.getName());
}

@Test
void shouldHandleEmptyCategory() {
Category cat = new Category();
assertEquals(null, cat.getName());
}
}