diff --git a/src/main/java/com/workshop/springbootrestapi/SpringBootRestapiApplication.java b/src/main/java/com/workshop/springbootrestapi/SpringBootRestapiApplication.java new file mode 100644 index 0000000..02e18f8 --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/SpringBootRestapiApplication.java @@ -0,0 +1,13 @@ +package com.workshop.springbootrestapi; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootRestapiApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootRestapiApplication.class, args); + } + +} diff --git a/src/main/java/com/workshop/springbootrestapi/controller/CustomerController.java b/src/main/java/com/workshop/springbootrestapi/controller/CustomerController.java new file mode 100644 index 0000000..62a691d --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/controller/CustomerController.java @@ -0,0 +1,50 @@ +package com.workshop.springbootrestapi.controller; + +import com.workshop.springbootrestapi.model.Customer; +import com.workshop.springbootrestapi.service.CustomerService; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/customers") +public class CustomerController { + + private final CustomerService customerService; + + public CustomerController(CustomerService customerService) { + this.customerService = customerService; + } + + // CREATE + @PostMapping + public Customer create(@Valid @RequestBody Customer customer) { + return customerService.addCustomer(customer); + } + + // GET ALL + @GetMapping + public List getAll() { + return customerService.getAllCustomers(); + } + + // GET BY EMAIL + @GetMapping("/{email}") + public Customer getByEmail(@PathVariable String email) { + return customerService.getCustomerByEmail(email); + } + + // UPDATE + @PutMapping("/{email}") + public Customer update(@PathVariable String email, + @RequestBody Customer customer) { + return customerService.updateCustomer(email, customer); + } + + // DELETE + @DeleteMapping("/{email}") + public void delete(@PathVariable String email) { + customerService.deleteCustomer(email); + } +} \ No newline at end of file diff --git a/src/main/java/com/workshop/springbootrestapi/controller/ProductController.java b/src/main/java/com/workshop/springbootrestapi/controller/ProductController.java new file mode 100644 index 0000000..c6a1257 --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/controller/ProductController.java @@ -0,0 +1,89 @@ +package com.workshop.springbootrestapi.controller; + +import com.workshop.springbootrestapi.model.Product; +import com.workshop.springbootrestapi.service.ProductService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/products") +public class ProductController { + + private final ProductService productService; + private static final String API_KEY = "123456"; + + public ProductController(ProductService productService) { + this.productService = productService; + } + + private void validateApiKey(String apiKey) { + if (!API_KEY.equals(apiKey)) { + throw new RuntimeException("Invalid API Key"); + } + } + + @PostMapping + public ResponseEntity create( + @RequestHeader("API-Key") String apiKey, + @Valid @RequestBody Product product) { + + validateApiKey(apiKey); + return ResponseEntity.ok(productService.addProduct(product)); + } + + @GetMapping + public List getAll(@RequestHeader("API-Key") String apiKey) { + validateApiKey(apiKey); + return productService.getAllProducts(); + } + + @GetMapping("/{name}") + public Product getByName( + @RequestHeader("API-Key") String apiKey, + @PathVariable String name) { + + validateApiKey(apiKey); + return productService.getProductByName(name); + } + + @PutMapping("/{name}") + public Product update( + @RequestHeader("API-Key") String apiKey, + @PathVariable String name, + @RequestBody Product product) { + + validateApiKey(apiKey); + return productService.updateProduct(name, product); + } + + @DeleteMapping("/{name}") + public void delete( + @RequestHeader("API-Key") String apiKey, + @PathVariable String name) { + + validateApiKey(apiKey); + productService.deleteProduct(name); + } + + @GetMapping("/category/{category}") + public List getByCategory( + @RequestHeader("API-Key") String apiKey, + @PathVariable String category) { + + validateApiKey(apiKey); + return productService.getProductsByCategory(category); + } + + @GetMapping("/price") + public List getByPriceRange( + @RequestHeader("API-Key") String apiKey, + @RequestParam double min, + @RequestParam double max) { + + validateApiKey(apiKey); + return productService.getProductsByPriceRange(min, max); + } +} \ No newline at end of file diff --git a/src/main/java/com/workshop/springbootrestapi/exception/GlobalExceptionHandler.java b/src/main/java/com/workshop/springbootrestapi/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..08b8d79 --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/exception/GlobalExceptionHandler.java @@ -0,0 +1,43 @@ +package com.workshop.springbootrestapi.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingRequestHeaderException; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidation(MethodArgumentNotValidException ex) { + + Map errors = new HashMap<>(); + + ex.getBindingResult().getFieldErrors() + .forEach(err -> errors.put(err.getField(), err.getDefaultMessage())); + + return ResponseEntity.badRequest().body(errors); + } + + @ExceptionHandler(MissingRequestHeaderException.class) + public ResponseEntity handleMissingHeader(MissingRequestHeaderException ex) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body("API Key is required"); + } + + @ExceptionHandler(ProductNotFoundException.class) + public ResponseEntity handleProductNotFound(ProductNotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ex.getMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ex.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/com/workshop/springbootrestapi/exception/ProductNotFoundException.java b/src/main/java/com/workshop/springbootrestapi/exception/ProductNotFoundException.java new file mode 100644 index 0000000..8c51cb6 --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/exception/ProductNotFoundException.java @@ -0,0 +1,7 @@ +package com.workshop.springbootrestapi.exception; + +public class ProductNotFoundException extends RuntimeException { + public ProductNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/com/workshop/springbootrestapi/model/Customer.java b/src/main/java/com/workshop/springbootrestapi/model/Customer.java new file mode 100644 index 0000000..4be889c --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/model/Customer.java @@ -0,0 +1,60 @@ +package com.workshop.springbootrestapi.model; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; + +public class Customer { + + @NotBlank(message = "Name cannot be blank") + private String name; + + @Email(message = "Email must be valid") + private String email; + + @Min(value = 18, message = "Age must be at least 18") + private int age; + + @NotBlank(message = "Address cannot be blank") + private String address; + + + public Customer(String name, String email, int age, String address) { + this.name = name; + this.email = email; + this.age = age; + this.address = address; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public int getAge() { + return age; + } + + public String getAddress() { + return address; + } + + public void setName(String name) { + this.name = name; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setAge(int age) { + this.age = age; + } + + public void setAddress(String address) { + this.address = address; + } +} \ No newline at end of file diff --git a/src/main/java/com/workshop/springbootrestapi/model/Product.java b/src/main/java/com/workshop/springbootrestapi/model/Product.java new file mode 100644 index 0000000..347f1f2 --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/model/Product.java @@ -0,0 +1,65 @@ +package com.workshop.springbootrestapi.model; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; + +import java.math.BigDecimal; + +public class Product { + + @NotBlank(message = "Name cannot be blank") + @Size(min = 3, message = "Name must be at least 3 characters") + private String name; + + @Positive(message = "Price must be positive") + private BigDecimal price; + + @NotBlank(message = "Category cannot be blank") + private String category; + + @Positive(message = "Quantity must be positive") + private int quantity; + + public Product() {} + + public Product(String name, BigDecimal price, String category, int quantity) { + this.name = name; + this.price = price; + this.category = category; + this.quantity = quantity; + } + + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public String getCategory() { + return category; + } + + public int getQuantity() { + return quantity; + } + + public void setName(String name) { + this.name = name; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } + + public void setCategory(String category) { + this.category = category; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } +} \ No newline at end of file diff --git a/src/main/java/com/workshop/springbootrestapi/service/CustomerService.java b/src/main/java/com/workshop/springbootrestapi/service/CustomerService.java new file mode 100644 index 0000000..fb74b76 --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/service/CustomerService.java @@ -0,0 +1,44 @@ +package com.workshop.springbootrestapi.service; + +import com.workshop.springbootrestapi.model.Customer; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class CustomerService { + + private final List customers = new ArrayList<>(); + + public Customer addCustomer(Customer customer) { + customers.add(customer); + return customer; + } + + public List getAllCustomers() { + return customers; + } + + public Customer getCustomerByEmail(String email) { + return customers.stream() + .filter(c -> c.getEmail().equalsIgnoreCase(email)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Customer not found: " + email)); + } + + public Customer updateCustomer(String email, Customer updatedCustomer) { + Customer customer = getCustomerByEmail(email); + + customer.setName(updatedCustomer.getName()); + customer.setAge(updatedCustomer.getAge()); + customer.setAddress(updatedCustomer.getAddress()); + + return customer; + } + + public void deleteCustomer(String email) { + Customer customer = getCustomerByEmail(email); + customers.remove(customer); + } +} \ No newline at end of file diff --git a/src/main/java/com/workshop/springbootrestapi/service/ProductService.java b/src/main/java/com/workshop/springbootrestapi/service/ProductService.java new file mode 100644 index 0000000..b4b37b1 --- /dev/null +++ b/src/main/java/com/workshop/springbootrestapi/service/ProductService.java @@ -0,0 +1,73 @@ +package com.workshop.springbootrestapi.service; + +import com.workshop.springbootrestapi.exception.ProductNotFoundException; +import com.workshop.springbootrestapi.model.Product; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class ProductService { + + private final List products = new ArrayList<>(List.of( + new Product("iPhone 15", new BigDecimal("999.99"), "Mobile", 10), + new Product("Samsung S24", new BigDecimal("899.99"), "Mobile", 15), + new Product("MacBook Pro", new BigDecimal("1999.99"), "Laptop", 5), + new Product("iPad Air", new BigDecimal("649.99"), "Tablet", 8), + new Product("Sony Headphones", new BigDecimal("399.99"), "Audio", 20) + )); + + public Product addProduct(Product product) { + products.add(product); + return product; + } + + public List getAllProducts() { + return products; + } + + public Product getProductByName(String name) { + return products.stream() + .filter(p -> p.getName().equalsIgnoreCase(name)) + .findFirst() + .orElseThrow(() -> + new ProductNotFoundException("Product not found: " + name)); + } + + public Product updateProduct(String name, Product updatedProduct) { + Product product = getProductByName(name); + + product.setName(updatedProduct.getName()); + product.setPrice(updatedProduct.getPrice()); + product.setCategory(updatedProduct.getCategory()); + product.setQuantity(updatedProduct.getQuantity()); + + return product; + } + + public void deleteProduct(String name) { + Product product = getProductByName(name); + products.remove(product); + } + + public List getProductsByCategory(String category) { + return products.stream() + .filter(p -> p.getCategory().equalsIgnoreCase(category)) + .collect(Collectors.toList()); + } + + public List getProductsByPriceRange(double min, double max) { + + if (min > max) { + throw new IllegalArgumentException("Min price cannot be greater than max price"); + } + + return products.stream() + .filter(p -> p.getPrice().compareTo(BigDecimal.valueOf(min)) >= 0 + && p.getPrice().compareTo(BigDecimal.valueOf(max)) <= 0) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..0ec7673 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=SpringBootRESTAPI diff --git a/src/main/resources/request.http b/src/main/resources/request.http new file mode 100644 index 0000000..cfaf6e9 --- /dev/null +++ b/src/main/resources/request.http @@ -0,0 +1,14 @@ +POST http://localhost:8080/customers +Content-Type: application/json + +{ + "name": "Laura", + "email": "laura@email.com", + "age": 25, + "address": "Montequinto" +} + +### +GET http://localhost:8080/customers + + diff --git a/src/test/java/com/workshop/springbootrestapi/SpringBootRestapiApplicationTests.java b/src/test/java/com/workshop/springbootrestapi/SpringBootRestapiApplicationTests.java new file mode 100644 index 0000000..714f69f --- /dev/null +++ b/src/test/java/com/workshop/springbootrestapi/SpringBootRestapiApplicationTests.java @@ -0,0 +1,13 @@ +package com.workshop.springbootrestapi; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringBootRestapiApplicationTests { + + @Test + void contextLoads() { + } + +}