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

08-create-category #8

Merged
merged 16 commits into from
Sep 19, 2023
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: 0 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,20 @@
</properties>

<dependencies>

<!-- Exclude Logback -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!-- Can delete this if smth went wrong !!! -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
<!-- Exclude Logback -->
</dependency>
<!-- Add Log4j 2 Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Add Log4j 2 Can delete this if smth went wrong -->


<dependency>
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/spring/boot/bookstore/controller/BookController.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ public class BookController {
@GetMapping
@Operation(summary = "Get all books from the library",
description = "Retrieves a paginated list of all available books in the library."
+ "Requires authentication for authorized access."
)
public List<BookResponseDto> getAll(Pageable pageable) {
return bookService.findAll(pageable);
}

@GetMapping("/{id}")
@Operation(summary = "Get a book by its ID",
description = "Retrieves a book from the library by its unique identifier (ID)."
@Operation(summary = "Retrieve a book by ID",
description = "Get book details using its unique identifier (ID). "
+ "Requires authentication for authorized access."
)
public BookResponseDto getBookById(@PathVariable Long id) {
return bookService.getBookById(id);
Expand All @@ -52,16 +54,18 @@ public BookResponseDto getBookById(@PathVariable Long id) {
@PostMapping
@Operation(summary = "Create a new book",
description = "Adds a new book to the library's collection."
+ " Requires a valid book data payload in the request body.")
+ " Requires a valid book data payload in the request body."
+ "Requires authentication for authorized access.")
@PreAuthorize("hasRole('ADMIN')")
public BookResponseDto createBook(@RequestBody @Valid CreateBookRequestDto requestDto) {
logger.info("Received a request to create a book: {}", requestDto.getTitle());
return bookService.createBook(requestDto);
return bookService.save(requestDto);
}

@GetMapping("/title")
@Operation(summary = "Get books by title",
description = "Retrieves a list of books in the library that match the specified title."
+ "Requires authentication for authorized access."
)
public List<BookResponseDto> getAllByTitle(@RequestParam String title) {
return bookService.getBookByTitle(title);
Expand All @@ -70,6 +74,7 @@ public List<BookResponseDto> getAllByTitle(@RequestParam String title) {
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(summary = "Delete a book by its ID",
description = "Soft deletes a book from the library by its unique identifier (ID)."
+ "Requires authentication for authorized access."
)
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
Expand All @@ -93,8 +98,8 @@ public BookResponseDto updateById(@PathVariable Long id,
+ "various search parameters such as title, author, or genre."
)
@GetMapping("/search")
@PreAuthorize("hasRole('USER')")
public List<BookResponseDto> searchBooks(BookSearchParameter searchParameters) {
return bookService.searchBooks(searchParameters);
public List<BookResponseDto> searchBooks(BookSearchParameter searchParameters,
Pageable pageable) {
return bookService.searchBooks(searchParameters, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package spring.boot.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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import spring.boot.bookstore.dto.book.BookWithoutCategoryIdsDto;
import spring.boot.bookstore.dto.category.CategoryRequestDto;
import spring.boot.bookstore.dto.category.CategoryResponseDto;
import spring.boot.bookstore.service.book.BookService;
import spring.boot.bookstore.service.category.CategoryService;

@Tag(name = "Category Controller",
description = "Endpoints for managing categories for existed Books in library")
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/categories")
public class CategoryController {

private static final Logger logger = LogManager.getLogger(BookController.class);
private final CategoryService categoryService;
private final BookService bookService;

@Operation(summary = "Create a Category",
description = "Creates a new category for books in the library.")
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public CategoryResponseDto createCategory(@RequestBody
@Valid CategoryRequestDto categoryRequestDto) {
logger.info("Creating a new category.");
return categoryService.save(categoryRequestDto);
}

@Operation(summary = "Get All categories ",
description = "Retrieves a list of all categories available for books.")
@GetMapping
public List<CategoryResponseDto> getAll(Pageable pageable) {
return categoryService.findAll(pageable);
}

@Operation(summary = "Get Category by ID",
description = "Retrieves a category by its unique identifier (ID).")
@GetMapping("/{id}")
public CategoryResponseDto getCategoryById(@PathVariable Long id) {
logger.info("Retrieving category with ID: " + id);
return categoryService.getById(id);
}

@Operation(summary = "Update a Category by its ID",
description = "Updates an existing category in the library"
+ " based on its unique identifier (ID).")
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public CategoryResponseDto updateCategory(@PathVariable Long id,
@RequestBody CategoryRequestDto categoryRequestDto) {
logger.info("Updating category with ID: " + id);
return categoryService.update(id, categoryRequestDto);
}

@Operation(summary = "Delete category by their ID",
description = "Soft deletes a category from the library by its unique identifier (ID).")
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize("hasRole('ADMIN')")
public void deleteCategory(@PathVariable Long id) {
logger.info("Deleting category with ID: " + id);
categoryService.deleteById(id);
}

@Operation(summary = "Get Books by Category ID",
description = "Retrieves a list of books belonging to a specific category.")
@GetMapping("/{id}/books")
public List<BookWithoutCategoryIdsDto> getBooksByCategoryId(@PathVariable Long id,
Pageable pageable) {
logger.info("Retrieving books for category with ID: " + id);
return bookService.findAllByCategoryId(id,pageable);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package spring.boot.bookstore.dto.book;

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

@Data
Expand All @@ -12,4 +13,5 @@ public class BookResponseDto {
private BigDecimal price;
private String description;
private String coverImage;
private Set<Long> categoryIds;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package spring.boot.bookstore.dto.book;

public record BookSearchParameter(String[] title, String[] author, String[] isbn,
String [] price, String description) {
String [] price, String description,String[] categories) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package spring.boot.bookstore.dto.book;

import java.math.BigDecimal;

public record BookWithoutCategoryIdsDto(Long id, String title, String author,
String isbn, BigDecimal price,
String description, String coverImage){}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Set;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import spring.boot.bookstore.validation.book.isbn.Isbn;
Expand All @@ -18,9 +19,8 @@ public class CreateBookRequestDto {
private String isbn;
@Min(0)
private BigDecimal price;

private String description;

private String coverImage;

@NotNull
private Set<Long> categoryIds;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package spring.boot.bookstore.dto.category;

import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

@Data
public class CategoryRequestDto {
@NotNull
@Length(min = 4, max = 255)
private String name;
@Length(max = 255)
private String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package spring.boot.bookstore.dto.category;

import lombok.Data;

@Data
public class CategoryResponseDto {
private Long id;
private String name;
private String description;
}
18 changes: 17 additions & 1 deletion src/main/java/spring/boot/bookstore/mapper/BookMapper.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
package spring.boot.bookstore.mapper;

import java.util.stream.Collectors;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import spring.boot.bookstore.config.MapperConfig;
import spring.boot.bookstore.dto.book.BookResponseDto;
import spring.boot.bookstore.dto.book.BookWithoutCategoryIdsDto;
import spring.boot.bookstore.dto.book.CreateBookRequestDto;
import spring.boot.bookstore.model.Book;
import spring.boot.bookstore.model.Category;

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

Book toModel(CreateBookRequestDto requestDto);
Book toModel(CreateBookRequestDto bookDto);

BookWithoutCategoryIdsDto toDtoWithoutCategories(Book book);

@AfterMapping
default void setCategoryIds(@MappingTarget BookResponseDto bookResponseDto, Book book) {
bookResponseDto.setCategoryIds(book.getCategories().stream()
.map(Category::getId)
.collect(Collectors.toSet()));
}
}
14 changes: 14 additions & 0 deletions src/main/java/spring/boot/bookstore/mapper/CategoryMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package spring.boot.bookstore.mapper;

import org.mapstruct.Mapper;
import spring.boot.bookstore.config.MapperConfig;
import spring.boot.bookstore.dto.category.CategoryRequestDto;
import spring.boot.bookstore.dto.category.CategoryResponseDto;
import spring.boot.bookstore.model.Category;

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

Category toEntity(CategoryRequestDto categoryDto);
}
19 changes: 18 additions & 1 deletion src/main/java/spring/boot/bookstore/model/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

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;
import org.apache.commons.lang3.builder.ToStringExclude;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

Expand All @@ -34,6 +42,15 @@ public class Book {
private String description;
@Column(name = "coverImage")
private String coverImage;
@Column(name = "is_deleted",nullable = false)

@ToStringExclude
@EqualsAndHashCode.Exclude
@ManyToMany (fetch = FetchType.EAGER) // added (fetch = FetchType.EAGER)
@JoinTable(name = "books_categories",
joinColumns = @JoinColumn(name = "books_id"),
inverseJoinColumns = @JoinColumn(name = "category_id"))
private Set<Category> categories = new HashSet<>();

@Column(name = "is_deleted", nullable = false)
private boolean isDeleted = false;
}
37 changes: 37 additions & 0 deletions src/main/java/spring/boot/bookstore/model/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package spring.boot.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.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

@Data
@NoArgsConstructor
@Entity
@Getter
@Setter
@SQLDelete(sql = "Update category SET is_deleted = true WHERE id=?")
@Where(clause = "is_deleted=false")
@Table(name = "category")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false, unique = true)
private String name;
private String description;
@Column(name = "is_deleted", nullable = false)
private boolean isDeleted = false;

public Category(Long id) {

}
}
Loading
Loading