Skip to content

Commit

Permalink
docs: describe BookController responses
Browse files Browse the repository at this point in the history
  • Loading branch information
oksana-miazina committed May 6, 2024
1 parent 969f118 commit fcbdb69
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 66 deletions.
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<maven.checkstyle.plugin.configLocation>checkstyle.xml</maven.checkstyle.plugin.configLocation>
<lombok.mapstruct.binding.version>0.2.0</lombok.mapstruct.binding.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<swagger.version>2.5.0</swagger.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -65,7 +66,7 @@
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
<version>${swagger.version}</version>
</dependency>

</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package mate.academy.bookstore;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@OpenAPIDefinition(
info = @Info(
title = "Book Shop API",
version = "0.1",
description = "APIs for managing book shop"
)
)
public class BookStoreApplication {
public static void main(String[] args) {
SpringApplication.run(BookStoreApplication.class, args);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mate.academy.bookstore.controller;

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;
Expand All @@ -9,12 +11,12 @@
import lombok.AllArgsConstructor;
import mate.academy.bookstore.dto.BookDto;
import mate.academy.bookstore.dto.BookRequestDto;
import mate.academy.bookstore.response.ErrorResponse;
import mate.academy.bookstore.response.ResponseHandler;
import mate.academy.bookstore.response.SuccessResponse;
import mate.academy.bookstore.service.BookService;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -34,24 +36,34 @@ public class BookController {

@GetMapping
@Operation(summary = "Get all books", description = "Get all books")
public ResponseEntity<SuccessResponse<List<BookDto>>> getAll(Pageable pageable) {
@ApiResponses(value = {
@ApiResponse(responseCode = "200", useReturnTypeSchema = true)
})
public SuccessResponse<List<BookDto>> getAll(Pageable pageable) {
return ResponseHandler.getSuccessResponse(
bookService.findAll(pageable));
}

@GetMapping("/{id}")
@Operation(summary = "Get book by id", description = "Get book by id")
public ResponseEntity<SuccessResponse<BookDto>> getBookById(@PathVariable Long id) {
@ApiResponses(value = {
@ApiResponse(responseCode = "200", useReturnTypeSchema = true),
@ApiResponse(responseCode = "404", content =
{ @Content(schema = @Schema(implementation = ErrorResponse.class)) }),
})
public SuccessResponse<BookDto> getBookById(@PathVariable Long id) {
return ResponseHandler.getSuccessResponse(
bookService.getById(id));
}

@PostMapping
@Operation(summary = "Create a new book", description = "Create a new book")
@ApiResponses(value = {
@ApiResponse(responseCode = "409", description = "Book with such ISBN already exists"),
@ApiResponse(responseCode = "201", useReturnTypeSchema = true),
@ApiResponse(responseCode = "409", description = "Book with such ISBN already exists",
content = { @Content(schema = @Schema(implementation = ErrorResponse.class)) }),
})
public ResponseEntity<SuccessResponse<BookDto>> createBook(
public SuccessResponse<BookDto> createBook(
@Valid @RequestBody BookRequestDto bookDto) {
return ResponseHandler.getSuccessResponse(
bookService.save(bookDto),
Expand All @@ -60,15 +72,27 @@ public ResponseEntity<SuccessResponse<BookDto>> createBook(

@PutMapping("/{id}")
@Operation(summary = "Update an existing book", description = "Update an existing book")
public ResponseEntity<SuccessResponse<BookDto>> updateBookById(@PathVariable Long id,
@RequestBody BookRequestDto bookRequestDto) {
@ApiResponses(value = {
@ApiResponse(responseCode = "200", useReturnTypeSchema = true),
@ApiResponse(responseCode = "409",
description = "Book with such ISBN already exists "
+ "(possible if new ISBN differs from old one)",
content = { @Content(schema = @Schema(implementation = ErrorResponse.class)) }),
})
public SuccessResponse<BookDto> updateBookById(@PathVariable Long id,
@Valid @RequestBody BookRequestDto bookDto) {
return ResponseHandler.getSuccessResponse(
bookService.updateById(id, bookRequestDto));
bookService.updateById(id, bookDto));
}

@DeleteMapping("/{id}")
@Operation(summary = "Delete book by id", description = "Delete book by id")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@ApiResponses(value = {
@ApiResponse(responseCode = "204", useReturnTypeSchema = true),
@ApiResponse(responseCode = "404", content =
{ @Content(schema = @Schema(implementation = ErrorResponse.class)) }),
})
public void deleteBookById(@PathVariable Long id) {
bookService.deleteById(id);
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/mate/academy/bookstore/dto/BookDto.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mate.academy.bookstore.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import lombok.Data;

Expand All @@ -8,7 +9,9 @@ public class BookDto {
private Long id;
private String title;
private String author;
@Schema(example = "ISBN-13: 978-0-596-52068-7")
private String isbn;
@Schema(example = "0.01")
private BigDecimal price;
private String description;
private String coverImage;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/mate/academy/bookstore/dto/BookRequestDto.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mate.academy.bookstore.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
Expand All @@ -22,6 +23,7 @@ public class BookRequestDto {

@NotBlank(message = "{validation.isbn.notempty}")
@Pattern(regexp = ISBN_PATTERN, message = "{validation.isbn.valid}")
@Schema(type = "string", example = "ISBN-13: 978-0-596-52068-7")
private String isbn;

@DecimalMin(value = PRICE_MIN, message = "{validation.price.valid}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import mate.academy.bookstore.response.ErrorResponse;
import mate.academy.bookstore.response.ResponseHandler;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
Expand All @@ -15,15 +13,21 @@

@RestControllerAdvice
public class CustomGlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ErrorResponse handleException(Exception ex) {
return ResponseHandler.getErrorResponse(
ex.getMessage(), HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ErrorResponse> handleEntityNotFoundException(
public ErrorResponse handleEntityNotFoundException(
EntityNotFoundException ex) {
return ResponseHandler.getErrorResponse(
ex.getMessage(), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationExceptions(
public ErrorResponse handleValidationExceptions(
MethodArgumentNotValidException ex) {
List<ErrorResponse.Message> errors = ex.getBindingResult().getAllErrors()
.stream()
Expand All @@ -33,15 +37,8 @@ public ResponseEntity<ErrorResponse> handleValidationExceptions(
errors, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(PropertyReferenceException.class)
public ResponseEntity<ErrorResponse> handlePropertyReferenceException(
PropertyReferenceException ex) {
return ResponseHandler.getErrorResponse(
ex.getMessage(), HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ErrorResponse> handleDataIntegrityViolationException(
public ErrorResponse handleDataIntegrityViolationException(
DataIntegrityViolationException ex) {
return ResponseHandler.getErrorResponse(
ex.getMessage(), HttpStatus.CONFLICT);
Expand Down
24 changes: 0 additions & 24 deletions src/main/java/mate/academy/bookstore/handler/ResponseData.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mate.academy.bookstore.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Getter;
import org.springframework.http.HttpStatus;
Expand All @@ -15,4 +16,9 @@ public ErrorResponse(List<Message> errors, HttpStatus status) {
super(status);
this.errors = errors;
}

@Schema(example = "false")
public boolean getSuccess() {
return isSuccess();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mate.academy.bookstore.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Date;
import lombok.Getter;
import org.springframework.http.HttpStatus;
Expand All @@ -11,6 +12,7 @@ public abstract class GeneralResponse {
private final int status;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
@Schema(type = "string", example = "06-05-2024 06:48:10")
private final Date timestamp;

public GeneralResponse(HttpStatus status) {
Expand Down
21 changes: 7 additions & 14 deletions src/main/java/mate/academy/bookstore/response/ResponseHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,24 @@

import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

public class ResponseHandler {
public static ResponseEntity<ErrorResponse> getErrorResponse(
public static ErrorResponse getErrorResponse(
String message, HttpStatus status) {
return new ResponseEntity<>(
new ErrorResponse(List.of(new ErrorResponse.Message(null, message)), status),
status);
return new ErrorResponse(List.of(new ErrorResponse.Message(null, message)), status);
}

public static ResponseEntity<ErrorResponse> getErrorResponse(
public static ErrorResponse getErrorResponse(
List<ErrorResponse.Message> messages, HttpStatus status) {
return new ResponseEntity<>(
new ErrorResponse(messages, status),
status);
return new ErrorResponse(messages, status);
}

public static <T> ResponseEntity<SuccessResponse<T>> getSuccessResponse(T data) {
public static <T> SuccessResponse<T> getSuccessResponse(T data) {
return getSuccessResponse(data, HttpStatus.OK);
}

public static <T> ResponseEntity<SuccessResponse<T>> getSuccessResponse(
public static <T> SuccessResponse<T> getSuccessResponse(
T data, HttpStatus status) {
return new ResponseEntity<>(
new SuccessResponse<>(data, status),
status);
return new SuccessResponse<>(data, status);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mate.academy.bookstore.service.impl;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.AllArgsConstructor;
import mate.academy.bookstore.dto.BookDto;
import mate.academy.bookstore.dto.BookRequestDto;
Expand Down Expand Up @@ -48,11 +50,21 @@ public BookDto getById(Long id) {
}

@Override
public BookDto updateById(Long id, BookRequestDto bookRequestDto) {
getBookByIdOrThrowException(id);
Book book = bookMapper.toModel(bookRequestDto);
book.setId(id);
return bookMapper.toDto(bookRepository.save(book));
public BookDto updateById(Long id, BookRequestDto bookDto) {
Book book = getBookByIdOrThrowException(id);

if (!book.getIsbn().equals(bookDto.getIsbn())) {
Optional<Book> bookByIsbn = bookRepository.findByIsbn(bookDto.getIsbn());
if (bookByIsbn.isPresent() && !Objects.equals(bookByIsbn.get().getId(), book.getId())) {
throw new DataIntegrityViolationException(
localeService.getMessage("exception.exists.book")
);
}
}

Book bookUpdated = bookMapper.toModel(bookDto);
bookUpdated.setId(id);
return bookMapper.toDto(bookRepository.save(bookUpdated));
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ validation.isbn.notempty=The ISBN is required
validation.isbn.valid=The ISBN is not valid
validation.price.valid=The price must be greater than 0

exception.exists.book=Book already exists.
exception.exists.book=The book with such an ISBN is already in the database.
exception.notfound.book=Can't find a book by id:
2 changes: 1 addition & 1 deletion src/main/resources/messages_uk.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ validation.isbn.notempty=Необхідно вказати ISBN
validation.isbn.valid=Номер ISBN недійсний
validation.price.valid=Ціна повинна бути більшою за 0

exception.exists.book=Ця книжка вже є в базі.
exception.exists.book=Книжка з таким ISBN вже є в базі.
exception.notfound.book=Не вдається знайти книгу за id:

0 comments on commit fcbdb69

Please sign in to comment.