Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Category entity with all related dto, service, mapper and controller. #12

Merged
merged 6 commits into from
May 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.bookstore.dto.book.BookDto;
import mate.academy.bookstore.dto.book.BookRequestDto;
import mate.academy.bookstore.dto.book.BookSearchParametersDto;
import mate.academy.bookstore.dto.book.CreateBookRequestDto;
import mate.academy.bookstore.service.BookService;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
Expand All @@ -29,13 +29,15 @@
public class BookController {
private final BookService bookService;

@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Get all books", description = "Get a list of all available books")
public List<BookDto> getAll(Pageable pageable) {
return bookService.findAll(pageable);
}

@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Get a book by ID", description = "Get a book by ID, if there is one")
Expand All @@ -47,7 +49,7 @@ public BookDto getBookById(@PathVariable Long id) {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Create a new book", description = "Create a new book with generated ID")
public BookDto createBook(@RequestBody @Valid BookRequestDto requestDto) {
public BookDto createBook(@RequestBody @Valid CreateBookRequestDto requestDto) {
return bookService.save(requestDto);
}

Expand All @@ -56,7 +58,7 @@ public BookDto createBook(@RequestBody @Valid BookRequestDto requestDto) {
@ResponseStatus(HttpStatus.ACCEPTED)
@Operation(summary = "Update a book by ID", description = "Update a book by its ID, if exists")
public BookDto updateBook(@PathVariable Long id,
@RequestBody @Valid BookRequestDto requestDto) {
@RequestBody @Valid CreateBookRequestDto requestDto) {
return bookService.updateById(id, requestDto);
}

Expand All @@ -68,6 +70,7 @@ public void deleteBook(@PathVariable Long id) {
bookService.delete(id);
}

@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping("/search")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Get a list of books by search parameters",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package mate.academy.bookstore.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.bookstore.dto.book.BookDtoWithoutCategoryIds;
import mate.academy.bookstore.dto.category.CategoryDto;
import mate.academy.bookstore.service.BookService;
import mate.academy.bookstore.service.CategoryService;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
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.RestController;

@RequiredArgsConstructor
@RestController
@Tag(name = "Category management", description = "Endpoints for category management")
@RequestMapping(value = "/categories")
public class CategoryController {
private final CategoryService categoryService;
private final BookService bookService;

@PreAuthorize("hasRole('ADMIN')")
@PostMapping
@Operation(summary = "Create new category", description = "Create category with generated ID")
public CategoryDto createCategory(@RequestBody @Valid CategoryDto categoryDto) {
return categoryService.save(categoryDto);
}

@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping
@Operation(summary = "Get all categories", description = "Get a list of existing categories")
public List<CategoryDto> getAll(Pageable pageable) {
return categoryService.findAll(pageable);
}

@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping("/{id}")
@Operation(summary = "Get category by ID", description = "Get category by ID, if there is one")
public CategoryDto getCategoryById(@PathVariable Long id) {
return categoryService.getById(id);
}

@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/{id}")
@Operation(summary = "Update category by ID", description = "Update category by ID, if exists")
public CategoryDto updateCategory(@PathVariable Long id,
@RequestBody @Valid CategoryDto categoryDto) {
return categoryService.update(id, categoryDto);
}

@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
@Operation(summary = "Delete category by ID", description = "Delete category by ID, if exists")
public void deleteCategory(@PathVariable Long id) {
categoryService.delete(id);
}

@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@GetMapping("/{id}/books")
@Operation(summary = "Get books by category ID",
description = "Get a list of books by category ID")
public List<BookDtoWithoutCategoryIds> getBooksByCategoryId(
@PathVariable Long id,
Pageable pageable
) {
return bookService.getAllBookByCategoryId(id, pageable);
}
}
2 changes: 2 additions & 0 deletions src/main/java/mate/academy/bookstore/dto/book/BookDto.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mate.academy.bookstore.dto.book;

import java.math.BigDecimal;
import java.util.Set;
import lombok.Data;

@Data
Expand All @@ -12,4 +13,5 @@ public class BookDto {
private BigDecimal price;
private String description;
private String coverImage;
private Set<Long> categories;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package mate.academy.bookstore.dto.book;

import java.math.BigDecimal;
import lombok.Data;

@Data
public class BookDtoWithoutCategoryIds {
private Long id;
private String title;
private String author;
private String isbn;
private BigDecimal price;
private String description;
private String coverImage;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import org.hibernate.validator.constraints.ISBN;

@Data
public class BookRequestDto {
public class CreateBookRequestDto {
@NotBlank
private String title;
@NotBlank
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/mate/academy/bookstore/dto/category/CategoryDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.bookstore.dto.category;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record CategoryDto(
Long id,
@NotBlank
@Size(min = 4, max = 24, message = "Category name must be 4 to 24 characters long")
String name,
String description
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.bookstore.exception;

public class DuplicateEntityException extends RuntimeException {
public DuplicateEntityException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ protected ResponseEntity<Object> handleMethodRepository(DuplicateIsbnException e
return getResponseEntity(HttpStatus.BAD_REQUEST, ex.getMessage());
}

@ExceptionHandler(DuplicateEntityException.class)
protected ResponseEntity<Object> handleMethodRepository(DuplicateEntityException ex) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
protected ResponseEntity<Object> handleMethodRepository(DuplicateEntityException ex) {
protected ResponseEntity<Object> handleDuplicateEntity(DuplicateEntityException ex) {

return getResponseEntity(HttpStatus.BAD_REQUEST, ex.getMessage());
}

@ExceptionHandler(EntityNotFoundException.class)
protected ResponseEntity<Object> handleMethodNotFound(EntityNotFoundException ex) {
return getResponseEntity(HttpStatus.NOT_FOUND, ex.getMessage());
Expand Down
23 changes: 21 additions & 2 deletions src/main/java/mate/academy/bookstore/mapper/BookMapper.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
package mate.academy.bookstore.mapper;

import java.util.Collections;
import java.util.stream.Collectors;
import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.book.BookDto;
import mate.academy.bookstore.dto.book.BookRequestDto;
import mate.academy.bookstore.dto.book.BookDtoWithoutCategoryIds;
import mate.academy.bookstore.dto.book.CreateBookRequestDto;
import mate.academy.bookstore.model.Book;
import mate.academy.bookstore.model.Category;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;

@Mapper(config = MapperConfig.class)
public interface BookMapper {
@Mapping(target = "categories", ignore = true)
BookDto toDto(Book book);

@Mapping(target = "id", ignore = true)
@Mapping(target = "deleted", ignore = true)
Book toModel(BookRequestDto requestDto);
Book toEntity(CreateBookRequestDto requestDto);

BookDtoWithoutCategoryIds toDtoWithoutCategories(Book book);

@AfterMapping
default void setCategoryIds(@MappingTarget BookDto bookDto, Book book) {
if (book.getCategories() == null) {
bookDto.setCategories(Collections.emptySet());
}
bookDto.setCategories(book.getCategories().stream()
.map(Category::getId)
.collect(Collectors.toSet()));
}
}
13 changes: 13 additions & 0 deletions src/main/java/mate/academy/bookstore/mapper/CategoryMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.bookstore.mapper;

import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.category.CategoryDto;
import mate.academy.bookstore.model.Category;
import org.mapstruct.Mapper;

@Mapper(config = MapperConfig.class)
public interface CategoryMapper {
CategoryDto toDto(Category category);

Category toEntity(CategoryDto categoryDto);
}
15 changes: 14 additions & 1 deletion src/main/java/mate/academy/bookstore/model/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
Expand All @@ -15,7 +21,7 @@

@Getter
@Setter
@EqualsAndHashCode(of = {"id", "title", "author", "isbn"})
@EqualsAndHashCode(of = {"id", "title", "author", "isbn", "categories"})
@Entity
@SQLDelete(sql = "UPDATE books SET is_deleted = true WHERE id=?")
@SQLRestriction(value = "is_deleted=false")
Expand All @@ -40,6 +46,13 @@ public class Book {
private String description;

private String coverImage;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "books_categories",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "category_id")
)
private Set<Category> categories = new HashSet<>();

@Column(nullable = false)
private boolean isDeleted;
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/mate/academy/bookstore/model/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mate.academy.bookstore.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.SoftDelete;

@Getter
@Setter
@Entity
@SoftDelete
@Table(name = "categories")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
private String description;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package mate.academy.bookstore.repository.book;

import java.util.List;
import mate.academy.bookstore.model.Book;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {
@EntityGraph(attributePaths = {"categories"})
Book findBookByIsbn(String isbn);

@Query("SELECT b FROM Book b LEFT JOIN FETCH b.categories c WHERE c.id = :categoryId")
List<Book> findAllBooksByCategoryId(@Param("categoryId") Long categoryId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package mate.academy.bookstore.repository.category;

import mate.academy.bookstore.model.Category;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CategoryRepository extends JpaRepository<Category, Long> {

Category findByName(String categoryName);
}
9 changes: 6 additions & 3 deletions src/main/java/mate/academy/bookstore/service/BookService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@

import java.util.List;
import mate.academy.bookstore.dto.book.BookDto;
import mate.academy.bookstore.dto.book.BookRequestDto;
import mate.academy.bookstore.dto.book.BookDtoWithoutCategoryIds;
import mate.academy.bookstore.dto.book.BookSearchParametersDto;
import mate.academy.bookstore.dto.book.CreateBookRequestDto;
import org.springframework.data.domain.Pageable;

public interface BookService {
BookDto save(BookRequestDto requestDto);
BookDto save(CreateBookRequestDto requestDto);

List<BookDto> findAll(Pageable pageable);

BookDto findById(Long id);

List<BookDto> search(BookSearchParametersDto parametersDto, Pageable pageable);

BookDto updateById(Long id, BookRequestDto requestDto);
BookDto updateById(Long id, CreateBookRequestDto requestDto);

void delete(Long id);

List<BookDtoWithoutCategoryIds> getAllBookByCategoryId(Long id, Pageable pageable);
}
17 changes: 17 additions & 0 deletions src/main/java/mate/academy/bookstore/service/CategoryService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mate.academy.bookstore.service;

import java.util.List;
import mate.academy.bookstore.dto.category.CategoryDto;
import org.springframework.data.domain.Pageable;

public interface CategoryService {
List<CategoryDto> findAll(Pageable pageable);

CategoryDto getById(Long id);

CategoryDto save(CategoryDto categoryDto);

CategoryDto update(Long id, CategoryDto categoryDto);

void delete(Long id);
}
Loading
Loading