Skip to content
Merged
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Demonstração do uso de HATEOAS para um projeto Springboot REST

http://localhost:8080/

### Products

* CRUD Products

http://localhost:8080/products

## Endpoints API

### Home Message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -23,7 +24,7 @@

@RestController
@RequestMapping("/api/products")
public class ProductController {
public class ProductApi {

@Autowired
private ProductRepository productRepository;
Expand All @@ -46,7 +47,13 @@ public CollectionModel<EntityModel<ProductDTO>> getAllProducts() {
List<EntityModel<ProductDTO>> products = productRepository.findAll().stream().map(productDTOAssembler::toModel)
.collect(Collectors.toList());

return CollectionModel.of(products);
CollectionModel<EntityModel<ProductDTO>> collection = CollectionModel.of(products);

collection.add(WebMvcLinkBuilder.linkTo(
WebMvcLinkBuilder.methodOn(
ProductApi.class).getAllProducts()).withSelfRel());

return collection;
}

// READ - Obter um produto específico
Expand Down Expand Up @@ -79,7 +86,8 @@ public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
.orElseThrow(() -> new RuntimeException("Produto não encontrado"));
productRepository.delete(product);

//return ResponseEntity.ok("Produto excluído com sucesso");
return null;
//return ResponseEntity.ok("Produto excluído com sucesso"); alterar para ResponseEntity<String>
//return null;
return ResponseEntity.noContent().build(); // Retorna 204 sem conteúdo
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/pro/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.pro.controller;

import java.util.Map;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import com.pro.model.Product;

@Controller
@RequestMapping("/products")
public class ProductController {

private final RestTemplate restTemplate;

public ProductController(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}

@GetMapping({"", "/"})
public String listProducts(Model model) {

String apiUrl = "http://localhost:8080/api/products";

ResponseEntity<Map> response = restTemplate.getForEntity(apiUrl, Map.class);

model.addAttribute("apiResponse", response.getBody());

return "web/product/list";
}

@GetMapping("/edit")
public String editProduct(@RequestParam String href, Model model) {

ResponseEntity<Map> response = restTemplate.getForEntity(href, Map.class);

model.addAttribute("product", response.getBody());

return "web/product/form";
}

@PostMapping("/edit")
public String updateProduct(@RequestParam String href, @ModelAttribute Product product) {
restTemplate.put(href, product); // Atualiza o produto via PUT na API
return "redirect:/products"; // Redireciona para a lista de produtos
}

@GetMapping("/delete")
public String deleteProduct(@RequestParam String href) {

restTemplate.delete(href);

return "redirect:/products";
}
}
10 changes: 5 additions & 5 deletions src/main/java/com/pro/dto/assembler/ProductDTOAssembler.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.stereotype.Component;

import com.pro.api.ProductController;
import com.pro.api.ProductApi;
import com.pro.dto.ProductDTO;
import com.pro.model.Product;

Expand All @@ -24,18 +24,18 @@ public EntityModel<ProductDTO> toModel(Product product) {

// Link self (auto referenciado)
productResource.add(WebMvcLinkBuilder
.linkTo(WebMvcLinkBuilder.methodOn(ProductController.class).getProduct(product.getId())).withSelfRel());
.linkTo(WebMvcLinkBuilder.methodOn(ProductApi.class).getProduct(product.getId())).withSelfRel());

// Link para todos os produtos
productResource.add(WebMvcLinkBuilder
.linkTo(WebMvcLinkBuilder.methodOn(ProductController.class).getAllProducts()).withRel("all-products"));
.linkTo(WebMvcLinkBuilder.methodOn(ProductApi.class).getAllProducts()).withRel("all-products"));

// Link para atualizar o produto (update)
productResource.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ProductController.class)
productResource.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ProductApi.class)
.updateProduct(product.getId(), product)).withRel("update-product"));

// Link para deletar o produto (delete)
productResource.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ProductController.class)
productResource.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ProductApi.class)
.deleteProduct(product.getId())).withRel("delete-product"));

return productResource;
Expand Down
68 changes: 68 additions & 0 deletions src/main/resources/templates/web/product/form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta name="description" content="Pagina Inicial da aplicação">
<meta name="keywords" content="pagina-inicial, pagina-home, inicio, home, inicial, index, ...">
<meta name="author" content="Dev Academy">
<meta name="revisit-after" content="60 days">
<meta name="ROBOTS" content="INDEX, FOLLOW">
<link rel="icon" th:href="@{/ico/favicon.ico}">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Home | Inicio</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<style>
.body-align {
/* border: 2px solid #000000; */
padding-top: 2%;
padding-left: 5%;
padding-right: 5%;
text-align: center;
}
</style>
</head>
<body class="w3-light-grey">

<div class="w3-container w3-blue w3-padding">
<h2>Product | Início</h2>
</div>

<div class="w3-container w3-margin-top" style="min-height: 400px;">

<!-- Card principal -->
<div class="w3-card w3-white w3-padding w3-round-large">

<h3 class="w3-text-dark-grey">Editar Produto</h3>

<!-- Formulário de edição -->
<div class="w3-card w3-white w3-padding w3-round-large">

<h3 class="w3-text-dark-grey">Detalhes do Produto</h3>

<!-- Formulário de edição do produto -->
<form th:action="@{/products/edit(href=${product['_links']['self']['href']})}" method="post">

<div class="w3-margin-bottom">
<label for="name">Nome do Produto:</label>
<input class="w3-input w3-border w3-round" type="text" id="name" name="name" th:value="${product['name']}" required/>
</div>

<div class="w3-margin-bottom">
<label for="price">Preço:</label>
<input class="w3-input w3-border w3-round" type="number" id="price" name="price" th:value="${product['price']}" required/>
</div>

<div class="w3-center">
<button class="w3-button w3-blue w3-round" type="submit">Salvar alterações</button>
</div>
</form>

</div>

</div>
</div>
</body>
</html>
62 changes: 62 additions & 0 deletions src/main/resources/templates/web/product/list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta name="description" content="Pagina Inicial da aplicação">
<meta name="keywords" content="pagina-inicial, pagina-home, inicio, home, inicial, index, ...">
<meta name="author" content="Dev Academy">
<meta name="revisit-after" content="60 days">
<meta name="ROBOTS" content="INDEX, FOLLOW">
<link rel="icon" th:href="@{/ico/favicon.ico}">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Home | Inicio</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<style>
.body-align {
/* border: 2px solid #000000; */
padding-top: 2%;
padding-left: 5%;
padding-right: 5%;
text-align: center;
}
</style>
</head>
<body class="w3-light-grey">

<div class="w3-container w3-blue w3-padding">
<h2>Product | Início</h2>
</div>

<div class="w3-container w3-margin-top" style="min-height: 400px;">

<!-- Card principal -->
<div class="w3-card w3-white w3-padding w3-round-large">

<h3 class="w3-text-dark-grey">Lista de Produtos</h3>

<div class="w3-panel w3-yellow w3-round w3-margin-top"
th:if="${#lists.isEmpty(apiResponse['_embedded'])}">
Nenhum produto encontrado.
</div>

<div th:unless="${#lists.isEmpty(apiResponse['_embedded'])}">
<ul class="w3-ul w3-card-4">
<li th:each="prod : ${apiResponse['_embedded']['productDTOList']}">
<span th:text="${prod['name']}"></span> - <span th:text="${prod['price']}"></span>

<!-- Link de edição via HATEOAS -->
<a th:href="@{/products/edit(href=${prod['_links']['self']['href']})}">Editar</a>

<!-- Link de exclusão via HATEOAS -->
<a th:href="@{/products/delete(href=${prod['_links']['self']['href']})}">Excluir</a>
</li>
</ul>
</div>

</div>
</div>
</body>
</html>