diff --git a/api/src/main/java/com/orderflow/ecommerce/controllers/CategoryController.java b/api/src/main/java/com/orderflow/ecommerce/controllers/CategoryController.java index de5ecf8..8d02ed3 100644 --- a/api/src/main/java/com/orderflow/ecommerce/controllers/CategoryController.java +++ b/api/src/main/java/com/orderflow/ecommerce/controllers/CategoryController.java @@ -1,16 +1,8 @@ package com.orderflow.ecommerce.controllers; -import com.orderflow.ecommerce.dtos.ErrorResponse; +import com.orderflow.ecommerce.controllers.docs.CategoryControllerDocs; import com.orderflow.ecommerce.entities.Category; import com.orderflow.ecommerce.repositories.CategoryRepository; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -21,82 +13,46 @@ @RestController @RequestMapping(value = "/categories") -@Tag(name = "Categorias", description = "CRUD de categorias de produtos") -public class CategoryController { +public class CategoryController implements CategoryControllerDocs { @Autowired private CategoryRepository repository; + @Override @GetMapping - @Operation(summary = "Lista todas as categorias") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Lista obtida com sucesso", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = Category.class)))) - }) public ResponseEntity> findAll() { return ResponseEntity.ok().body(repository.findAll()); } + @Override @GetMapping(value = "/{id}") - @Operation(summary = "Obtém categoria por id") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Categoria encontrada", - content = @Content(schema = @Schema(implementation = Category.class))), - @ApiResponse(responseCode = "404", description = "Categoria inexistente", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) - public ResponseEntity findById( - @Parameter(description = "Identificador numérico da categoria", required = true, example = "1") - @PathVariable Long id) { + public ResponseEntity findById(@PathVariable Long id) { Category obj = repository.findById(id) - .orElseThrow(() -> new NoSuchElementException("Categoria não encontrada com o ID: " + id)); + .orElseThrow(() -> new NoSuchElementException("Categoria não encontrada com o ID: " + id)); return ResponseEntity.ok().body(obj); } + @Override @PostMapping - @Operation(summary = "Cria uma categoria") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Categoria criada e persistida", - content = @Content(schema = @Schema(implementation = Category.class))), - @ApiResponse(responseCode = "400", description = "Corpo inválido ou falha de validação", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) - public ResponseEntity insert( - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Dados da categoria (campo id pode ser omitido)", required = true) - @Valid @RequestBody Category obj) { + public ResponseEntity insert(@Valid @RequestBody Category obj) { return ResponseEntity.ok().body(repository.save(obj)); } + @Override @DeleteMapping(value = "/{id}") - @Operation(summary = "Remove categoria por id") - @ApiResponses({ - @ApiResponse(responseCode = "204", description = "Exclusão processada (idempotente se o registro não existir)") - }) - public ResponseEntity delete( - @Parameter(description = "Identificador da categoria a remover", required = true, example = "1") - @PathVariable Long id) { + public ResponseEntity delete(@PathVariable Long id) { repository.deleteById(id); return ResponseEntity.noContent().build(); } + @Override @PutMapping(value = "/{id}") - @Operation(summary = "Atualiza o nome de uma categoria") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Categoria atualizada", - content = @Content(schema = @Schema(implementation = Category.class))), - @ApiResponse(responseCode = "400", description = "Corpo inválido ou falha de validação", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))), - @ApiResponse(responseCode = "404", description = "Categoria inexistente", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) public ResponseEntity update( - @Parameter(description = "Identificador da categoria", required = true, example = "1") - @PathVariable Long id, - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Novos dados (normalmente apenas name)", required = true) - @Valid @RequestBody Category obj) { + @PathVariable Long id, + @Valid @RequestBody Category obj + ) { Category entity = repository.findById(id) - .orElseThrow(() -> new NoSuchElementException("Categoria não encontrada para atualizar")); - + .orElseThrow(() -> new NoSuchElementException("Categoria não encontrada para atualizar")); entity.setName(obj.getName()); return ResponseEntity.ok().body(repository.save(entity)); } diff --git a/api/src/main/java/com/orderflow/ecommerce/controllers/ProductController.java b/api/src/main/java/com/orderflow/ecommerce/controllers/ProductController.java index 93f5835..d0ad60f 100644 --- a/api/src/main/java/com/orderflow/ecommerce/controllers/ProductController.java +++ b/api/src/main/java/com/orderflow/ecommerce/controllers/ProductController.java @@ -1,16 +1,8 @@ package com.orderflow.ecommerce.controllers; -import com.orderflow.ecommerce.dtos.ErrorResponse; +import com.orderflow.ecommerce.controllers.docs.ProductControllerDocs; import com.orderflow.ecommerce.entities.Product; import com.orderflow.ecommerce.repositories.ProductRepository; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -21,79 +13,41 @@ @RestController @RequestMapping(value = "/products") -@Tag(name = "Produtos", description = "CRUD de produtos do catálogo") -public class ProductController { +public class ProductController implements ProductControllerDocs { @Autowired private ProductRepository repository; + @Override @GetMapping - @Operation(summary = "Lista todos os produtos") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Lista obtida com sucesso", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = Product.class)))) - }) public ResponseEntity> findAll() { return ResponseEntity.ok().body(repository.findAll()); } + @Override @GetMapping(value = "/{id}") - @Operation(summary = "Obtém produto por id") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Produto encontrado", - content = @Content(schema = @Schema(implementation = Product.class))), - @ApiResponse(responseCode = "404", description = "Produto inexistente", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) - public ResponseEntity findById( - @Parameter(description = "Identificador numérico do produto", required = true, example = "10") - @PathVariable Long id) { + public ResponseEntity findById(@PathVariable Long id) { Product obj = repository.findById(id) .orElseThrow(() -> new NoSuchElementException("Produto não encontrado")); return ResponseEntity.ok().body(obj); } + @Override @PostMapping - @Operation(summary = "Cria um produto") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Produto criado e persistido", - content = @Content(schema = @Schema(implementation = Product.class))), - @ApiResponse(responseCode = "400", description = "Corpo inválido ou falha de validação", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) - public ResponseEntity insert( - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Dados do produto; id pode ser omitido; category pode ser {\"id\": n}", required = true) - @Valid @RequestBody Product obj) { + public ResponseEntity insert(@Valid @RequestBody Product obj) { return ResponseEntity.ok().body(repository.save(obj)); } + @Override @DeleteMapping(value = "/{id}") - @Operation(summary = "Remove produto por id") - @ApiResponses({ - @ApiResponse(responseCode = "204", description = "Exclusão processada (idempotente se o registro não existir)") - }) - public ResponseEntity delete( - @Parameter(description = "Identificador do produto a remover", required = true, example = "10") - @PathVariable Long id) { + public ResponseEntity delete(@PathVariable Long id) { repository.deleteById(id); return ResponseEntity.noContent().build(); } + @Override @PutMapping(value = "/{id}") - @Operation(summary = "Atualiza um produto") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Produto atualizado", - content = @Content(schema = @Schema(implementation = Product.class))), - @ApiResponse(responseCode = "400", description = "Corpo inválido ou falha de validação", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))), - @ApiResponse(responseCode = "404", description = "Produto inexistente", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) - public ResponseEntity update( - @Parameter(description = "Identificador do produto", required = true, example = "10") - @PathVariable Long id, - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Campos a atualizar; category pode ser referência por id", required = true) - @Valid @RequestBody Product obj) { + public ResponseEntity update(@PathVariable Long id, @Valid @RequestBody Product obj) { Product entity = repository.findById(id) .orElseThrow(() -> new NoSuchElementException("Produto não encontrado para atualizar")); diff --git a/api/src/main/java/com/orderflow/ecommerce/controllers/TestController.java b/api/src/main/java/com/orderflow/ecommerce/controllers/TestController.java index 5785168..77b7dad 100644 --- a/api/src/main/java/com/orderflow/ecommerce/controllers/TestController.java +++ b/api/src/main/java/com/orderflow/ecommerce/controllers/TestController.java @@ -1,18 +1,12 @@ package com.orderflow.ecommerce.controllers; -import com.orderflow.ecommerce.dtos.ErrorResponse; +import com.orderflow.ecommerce.controllers.docs.TestControllerDocs; import com.orderflow.ecommerce.dtos.OrderItemDto; import com.orderflow.ecommerce.dtos.PingResponse; import com.orderflow.ecommerce.dtos.PublishSampleOrderResponse; import com.orderflow.ecommerce.entities.enums.OrderStatus; import com.orderflow.ecommerce.messaging.events.OrderCreatedEvent; import com.orderflow.ecommerce.messaging.publisher.OrderEventPublisher; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -24,8 +18,7 @@ @RestController @RequestMapping("/test") -@Tag(name = "Testes e integração", description = "Endpoints auxiliares para health check e publicação de eventos de exemplo no RabbitMQ") -public class TestController { +public class TestController implements TestControllerDocs { private static final long DEFAULT_START_ORDER_ID = 1000L; private final OrderEventPublisher orderEventPublisher; @@ -35,12 +28,8 @@ public TestController(OrderEventPublisher orderEventPublisher) { this.orderEventPublisher = orderEventPublisher; } + @Override @GetMapping("/ping") - @Operation(summary = "Verifica se a API está respondendo") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "API ativa", - content = @Content(schema = @Schema(implementation = PingResponse.class))) - }) public PingResponse ping() { return new PingResponse( "ok", @@ -49,15 +38,8 @@ public PingResponse ping() { ); } + @Override @GetMapping("/publish-sample-order") - @Operation(summary = "Publica um evento OrderCreated de exemplo no RabbitMQ", - description = "Útil para validar filas e consumidores (EmailConsumer, InventoryConsumer) sem fluxo real de pedido.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Evento enfileirado", - content = @Content(schema = @Schema(implementation = PublishSampleOrderResponse.class))), - @ApiResponse(responseCode = "500", description = "Erro interno ao publicar", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) public PublishSampleOrderResponse publishSampleOrder() { long nextOrderId = nextSampleOrderId(); var event = new OrderCreatedEvent( diff --git a/api/src/main/java/com/orderflow/ecommerce/controllers/docs/CategoryControllerDocs.java b/api/src/main/java/com/orderflow/ecommerce/controllers/docs/CategoryControllerDocs.java new file mode 100644 index 0000000..f79fabc --- /dev/null +++ b/api/src/main/java/com/orderflow/ecommerce/controllers/docs/CategoryControllerDocs.java @@ -0,0 +1,133 @@ +package com.orderflow.ecommerce.controllers.docs; + +import com.orderflow.ecommerce.dtos.ErrorResponse; +import com.orderflow.ecommerce.entities.Category; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +@Tag(name = "Category", description = "Endpoints para gerenciar Category - CRUD de categorias de produtos") +public interface CategoryControllerDocs { + + @Operation( + summary = "Lista todas as categorias", + description = "Retorna uma lista de todas as categorias existentes no sistema.", + responses = { + @ApiResponse( + responseCode = "200", + description = "Lista obtida com sucesso", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = Category.class))) + ) + } + ) + ResponseEntity> findAll(); + + @Operation( + summary = "Obtém categoria por id", + description = "Retorna uma categoria com base no seu ID fornecido.", + parameters = { + @Parameter( + name = "id", + description = "Identificador numérico de categoria", + required = true, + example = "1" + ) + }, + responses = { + @ApiResponse( + responseCode = "200", + description = "Categoria encontrada", + content = @Content(schema = @Schema(implementation = Category.class))), + @ApiResponse( + responseCode = "404", + description = "Categoria inexistente", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + } + ) + ResponseEntity findById(Long id); + + @Operation( + summary = "Cria uma categoria", + description = "Insere uma nova categoria no sistema. O campo 'id' deve ser omitido no envio.", + requestBody = @RequestBody( + description = "Dados da categoria a ser criada", + required = true, + content = @Content(schema = @Schema(implementation = Category.class)) + ), + responses = { + @ApiResponse( + responseCode = "200", + description = "Categoria criada e persistida com sucesso", + content = @Content(schema = @Schema(implementation = Category.class)) + ), + @ApiResponse( + responseCode = "400", + description = "Corpo inválido ou falha de validação", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + } + ) + ResponseEntity insert(Category obj); + + @Operation( + summary = "Remove categoria por id", + description = "Remove uma categoria existente do sistema de forma permanente.", + parameters = { + @Parameter( + name = "id", + description = "Identificador número da categoria a remover", + required = true, + example = "1" + ) + }, + responses = { + @ApiResponse( + responseCode = "204", + description = "Exclusão processada (idempotente se o registro não existir)", + content = @Content + ) + } + ) + ResponseEntity delete(Long id); + + @Operation( + summary = "Atualiza o nome de uma categoria", + description = "Atualiza os dados de uma categoria existente com base no ID fornecido", + parameters = { + @Parameter( + name = "id", + description = "Identificador numérico da categoria", + required = true, + example = "1" + ) + }, + requestBody = @RequestBody( + description = "Novos dados para atualização da categoria", + required = true, + content = @Content(schema = @Schema(implementation = Category.class)) + ), + responses = { + @ApiResponse( + responseCode = "200", + description = "Categoria atualizada com sucesso", + content = @Content(schema = @Schema(implementation = Category.class))), + @ApiResponse( + responseCode = "400", + description = "Corpo inválido ou falha de validação nos dados enviados", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "Categoria inexistente ou não encontrada para o ID informado", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + } + ) + ResponseEntity update (Long id, Category obj); +} diff --git a/api/src/main/java/com/orderflow/ecommerce/controllers/docs/ProductControllerDocs.java b/api/src/main/java/com/orderflow/ecommerce/controllers/docs/ProductControllerDocs.java new file mode 100644 index 0000000..08fe007 --- /dev/null +++ b/api/src/main/java/com/orderflow/ecommerce/controllers/docs/ProductControllerDocs.java @@ -0,0 +1,129 @@ +package com.orderflow.ecommerce.controllers.docs; + +import com.orderflow.ecommerce.dtos.ErrorResponse; +import com.orderflow.ecommerce.entities.Product; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +@Tag(name = "Product", description = "Endpoints para gerenciar Product - CRUD de produtos do catálogo") +public interface ProductControllerDocs { + + @Operation( + summary = "Lista todos os produtos", + description = "Retorna uma lista de todos os produtos existentes no sistema", + responses = { + @ApiResponse( + responseCode = "200", + description = "Lista obtida com sucesso", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = Product.class))) + ) + } + ) + ResponseEntity> findAll(); + + @Operation( + summary = "Obtém produto por id", + description = "Retorna um produto com base no seu ID fornecido.", + parameters = { + @Parameter( + name = "id", + description = "Identificador numérico do produto", + required = true, + example = "10") + }, + responses = { + @ApiResponse( + responseCode = "200", + description = "Produto encontrado com sucesso", + content = @Content(schema = @Schema(implementation = Product.class))), + @ApiResponse( + responseCode = "404", + description = "Produto inexistente ou não encontrado para o ID informado", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + } + ) + ResponseEntity findById(Long id); + + @Operation( + summary = "Cria um produto", + description = "Insere um novo produto no sistema. O campo 'id' deve ser omitido no envio.", + requestBody = @RequestBody( + description = "Dados do produto a ser criado", + required = true, + content = @Content(schema = @Schema(implementation = Product.class)) + ), + responses = { + @ApiResponse( + responseCode = "200", + description = "Produto criado e persistido com sucesso", + content = @Content(schema = @Schema(implementation = Product.class))), + @ApiResponse( + responseCode = "400", + description = "Corpo inválido ou falha de validação nos dados enviados", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + } + ) + ResponseEntity insert(Product obj); + + @Operation( + summary = "Remove produto por id", + description = "Remove um produto existente do sistema de forma permanente.", + parameters = { + @Parameter( + name = "id", + description = "Identificador do produto a remover", + required = true, + example = "10") + }, + responses = { + @ApiResponse( + responseCode = "204", + description = "Exclusão processada (idempotente se o registro não existir)", + content = @Content + ) + } + ) + ResponseEntity delete(Long id); + + @Operation( + summary = "Atualiza um produto", + description = "Atualiza os dados de um produto existente com base no ID fornecido", + parameters = { + @Parameter( + name = "id", + description = "Identificador do produto", + required = true, + example = "10" + ) + }, + requestBody = @RequestBody( + description = "Novos dados para atualização de produto", + required = true, + content = @Content(schema = @Schema(implementation = Product.class)) + ), + responses = { + @ApiResponse( + responseCode = "200", + description = "Produto atualizado com sucesso", + content = @Content(schema = @Schema(implementation = Product.class))), + @ApiResponse( + responseCode = "400", + description = "Corpo inválido ou falha de validação nos dados enviados", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "Produto inexistente ou não encontrado para o ID informado", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + } + ) + ResponseEntity update (Long id, Product obj); +} diff --git a/api/src/main/java/com/orderflow/ecommerce/controllers/docs/TestControllerDocs.java b/api/src/main/java/com/orderflow/ecommerce/controllers/docs/TestControllerDocs.java new file mode 100644 index 0000000..88a83af --- /dev/null +++ b/api/src/main/java/com/orderflow/ecommerce/controllers/docs/TestControllerDocs.java @@ -0,0 +1,42 @@ +package com.orderflow.ecommerce.controllers.docs; + +import com.orderflow.ecommerce.dtos.ErrorResponse; +import com.orderflow.ecommerce.dtos.PingResponse; +import com.orderflow.ecommerce.dtos.PublishSampleOrderResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Test and integration", description = "Endpoints auxiliares para health check e publicação de eventos de exemplo no RabbitMQ") +public interface TestControllerDocs { + + @Operation( + summary = "Verifica se a API está respondendo", + description = "Endpoint de health check para checar a disponibilidade da aplicação.", + responses = { + @ApiResponse( + responseCode = "200", + description = "API ativa", + content = @Content(schema = @Schema(implementation = PingResponse.class))) + } + ) + PingResponse ping(); + + @Operation( + summary = "Publica um evento OrderCreated de exemplo no RabbitMQ", + description = "Útil para validar filas e consumidores (EmailConsumer, InventoryConsumer) sem fluxo real de pedido.", + responses = { + @ApiResponse( + responseCode = "200", + description = "Evento enfileirado", + content = @Content(schema = @Schema(implementation = PublishSampleOrderResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Erro interno ao publicar", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + } + ) + PublishSampleOrderResponse publishSampleOrder(); +}