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
207 changes: 129 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,129 @@
![logo_ironhack_blue 7](https://user-images.githubusercontent.com/23629340/40541063-a07a0a8a-601a-11e8-91b5-2f13e4e6b441.png)

# LAB | SpringBoot REST API

### Instructions

1. Fork this repo.
2. Clone your fork to your local machine.
3. Solve the challenges.


## Deliverables

- Upon completion, add your solution to git.
- Then commit to git and push to your repo on GitHub.
- Make a pull request and paste the pull request link in the submission field in the Student Portal.

## Tasks

1. Create a Spring Boot application using Spring Initializr with the following dependencies:
- Spring Web
- Spring Boot DevTools
- Spring Boot Starter Validation

2. Create a `Product` class with the following validated properties:
- name (not blank, min length 3)
- price (positive number)
- category (not blank)
- quantity (positive number)

3. Create a `ProductService` class that manages a List of Products and has methods to:
- Add a new product
- Get all products
- Get product by name
- Update product
- Delete product
- Get products by category
- Get products by price range

4. Create a `ProductController` class that:
- Uses constructor injection for the ProductService
- Requires an "API-Key" header for all requests (value: "123456")
- Has the following endpoints:
* POST `/products` - Create new product
* GET `/products` - Get all products
* GET `/products/{name}` - Get product by name
* PUT `/products/{name}` - Update product
* DELETE `/products/{name}` - Delete product
* GET `/products/category/{category}` - Get products by category
* GET `/products/price?min={min}&max={max}` - Get products by price range

5. Create a global exception handler that handles:
- Validation errors (return proper error messages)
- Missing API-Key header
- Product not found
- Invalid price range

6. Create a `Customer` class with the following validated properties:
- name (not blank)
- email (valid email format)
- age (minimum 18)
- address (not blank)

7. Create a `CustomerController` with endpoints to:
- Create new customer (with validation)
- Get all customers
- Get customer by email
- Update customer
- Delete customer

**Remember**:
- Use proper package structure
- Use constructor injection instead of @Autowired
- Test all endpoints using Postman
- Include appropriate error handling
- Use meaningful variable and method names
- Return appropriate HTTP status codes
- Include validation messages in responses
# Spring Boot REST API Lab 05 Final

Aplicacion REST desarrollada con Spring Boot para gestionar productos y clientes en memoria.

## Tecnologias

- Java 17
- Spring Boot 3.3.5
- Spring Web
- Spring Boot DevTools
- Spring Boot Starter Validation
- Maven

## Requisitos

- API-Key obligatoria en todas las peticiones: `123456`
- Datos validados en productos y clientes
- Manejo global de errores con respuestas JSON

## Estructura

- `model`: clases `Product` y `Customer`
- `service`: logica de negocio en memoria
- `controller`: endpoints REST
- `config`: interceptor de API key
- `exception`: excepciones personalizadas y handler global

## Endpoints

### Products

- `POST /products`
- `GET /products`
- `GET /products/{name}`
- `PUT /products/{name}`
- `DELETE /products/{name}`
- `GET /products/category/{category}`
- `GET /products/price?min={min}&max={max}`

### Customers

- `POST /customers`
- `GET /customers`
- `GET /customers/{email}`
- `PUT /customers/{email}`
- `DELETE /customers/{email}`

## Ejemplo de headers

```http
API-Key: 123456
Content-Type: application/json
```

## Ejemplo de payloads

### Product

```json
{
"name": "Laptop",
"price": 1200,
"category": "Tech",
"quantity": 5
}
```

### Customer

```json
{
"name": "Ana",
"email": "ana@example.com",
"age": 30,
"address": "Madrid"
}
```

## Ejecutar el proyecto

```bash
mvn spring-boot:run
```

La API queda disponible en:

```text
http://localhost:8080
```

## Ejecutar tests

```bash
mvn test
```

## Validaciones implementadas

### Product

- `name`: obligatorio, minimo 3 caracteres
- `price`: numero positivo
- `category`: obligatoria
- `quantity`: numero positivo

### Customer

- `name`: obligatorio
- `email`: formato valido
- `age`: minimo 18
- `address`: obligatoria

## Manejo de errores

La API devuelve respuestas con codigo HTTP adecuado para:

- errores de validacion
- API-Key ausente
- API-Key invalida
- producto no encontrado
- cliente no encontrado
- rango de precios invalido
- parametros faltantes o con tipo invalido

## Verificacion realizada

- La aplicacion compila correctamente
- Los tests pasan correctamente
- Se verificaron endpoints clave en `localhost:8080`
54 changes: 54 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>

<groupId>org.example</groupId>
<artifactId>springboot-rest-api-lab05final</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-rest-api-lab05final</name>
<description>Spring Boot REST API lab solution</description>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
12 changes: 12 additions & 0 deletions src/main/java/org/example/ProductApiApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProductApiApplication {

public static void main(String[] args) {
SpringApplication.run(ProductApiApplication.class, args);
}
}
28 changes: 28 additions & 0 deletions src/main/java/org/example/config/ApiKeyInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.example.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class ApiKeyInterceptor implements HandlerInterceptor {

private static final String API_KEY_HEADER = "API-Key";
private static final String EXPECTED_API_KEY = "123456";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String apiKey = request.getHeader(API_KEY_HEADER);

if (apiKey == null || apiKey.isBlank()) {
throw new MissingApiKeyException("Falta el header API-Key");
}

if (!EXPECTED_API_KEY.equals(apiKey)) {
throw new InvalidApiKeyException("API-Key invalida");
}

return true;
}
}
8 changes: 8 additions & 0 deletions src/main/java/org/example/config/InvalidApiKeyException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.example.config;

public class InvalidApiKeyException extends RuntimeException {

public InvalidApiKeyException(String message) {
super(message);
}
}
8 changes: 8 additions & 0 deletions src/main/java/org/example/config/MissingApiKeyException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.example.config;

public class MissingApiKeyException extends RuntimeException {

public MissingApiKeyException(String message) {
super(message);
}
}
20 changes: 20 additions & 0 deletions src/main/java/org/example/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

private final ApiKeyInterceptor apiKeyInterceptor;

public WebConfig(ApiKeyInterceptor apiKeyInterceptor) {
this.apiKeyInterceptor = apiKeyInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiKeyInterceptor);
}
}
55 changes: 55 additions & 0 deletions src/main/java/org/example/controller/CustomerController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.example.controller;

import jakarta.validation.Valid;
import org.example.model.Customer;
import org.example.service.CustomerService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/customers")
public class CustomerController {

private final CustomerService customerService;

public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Customer createCustomer(@Valid @RequestBody Customer customer) {
return customerService.createCustomer(customer);
}

@GetMapping
public List<Customer> getAllCustomers() {
return customerService.getAllCustomers();
}

@GetMapping("/{email}")
public Customer getCustomerByEmail(@PathVariable String email) {
return customerService.getCustomerByEmail(email);
}

@PutMapping("/{email}")
public Customer updateCustomer(@PathVariable String email, @Valid @RequestBody Customer customer) {
return customerService.updateCustomer(email, customer);
}

@DeleteMapping("/{email}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteCustomer(@PathVariable String email) {
customerService.deleteCustomer(email);
}
}
Loading