diff --git a/.gitignore b/.gitignore index 2c4a283..a057160 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ SpringApp/data.mv.db +SpringApp/library/data.mv.db +SpringApp/library/data.trace.db diff --git a/SpringApp/library/src/main/java/com/ip/library/LibraryApplication.java b/SpringApp/library/src/main/java/com/ip/library/LibraryApplication.java index fe1bcc5..73bcb0a 100644 --- a/SpringApp/library/src/main/java/com/ip/library/LibraryApplication.java +++ b/SpringApp/library/src/main/java/com/ip/library/LibraryApplication.java @@ -1,5 +1,10 @@ package com.ip.library; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.modelmapper.internal.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,18 +54,35 @@ public class LibraryApplication implements CommandLineRunner { log.info("Create default authors values"); final var author1 = authorService.create(new AuthorEntity("author1")); final var author2 = authorService.create(new AuthorEntity("author2")); + + final List list1 = new ArrayList<>(); + final List list2 = new ArrayList<>(); + final List list3 = new ArrayList<>(); + + list1.add(author1); + list2.add(author2); + list3.add(author1); + list3.add(author2); log.info("Create default books values"); - final var book1 = bookService.create(new BookEntity("book1", type1, author1)); - final var book2 = bookService.create(new BookEntity("book2", type1, author2)); - final var book3 = bookService.create(new BookEntity("book3", type2, author1)); - final var book4 = bookService.create(new BookEntity("book4", type2, author2)); + final var book1 = bookService.create(new BookEntity("book1", type1, list1)); + final var book2 = bookService.create(new BookEntity("book2", type1, list2)); + final var book3 = bookService.create(new BookEntity("book3", type2, list3)); + final var book4 = bookService.create(new BookEntity("book4", type2)); log.info("Create default users values"); final var user1 = userService.create(new UserEntity("user1", "123")); final var user2 = userService.create(new UserEntity("user2", "123")); final var admin1 = userService.create(new UserEntity("admin1", "123")); + userService.giveAdminRole(admin1.getId()); + + userService.addFavorite(user1.getId(), book1.getId()); + userService.addFavorite(user1.getId(), book2.getId()); + userService.addFavorite(user1.getId(), book3.getId()); + userService.addFavorite(user1.getId(), book4.getId()); + userService.addFavorite(user2.getId(), book1.getId()); + userService.addFavorite(user2.getId(), book2.getId()); } } } diff --git a/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorController.java b/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorController.java index 7e9ea45..22f1a66 100644 --- a/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorController.java +++ b/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorController.java @@ -15,8 +15,11 @@ import org.springframework.web.bind.annotation.RestController; import com.ip.library.core.configuration.Constants; import com.ip.library.authors.model.AuthorEntity; import com.ip.library.authors.service.AuthorService; +import com.ip.library.books.model.BookEntity; import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.RequestParam; + @RestController @RequestMapping(Constants.API_URL + "/author") @@ -61,4 +64,10 @@ public class AuthorController { public AuthorDto delete(@PathVariable(name = "id") Long id) { return toDto(authorService.delete(id)); } + + @GetMapping("/{id}/books") + public List getAuthorBooks(@PathVariable(name = "id") Long id) { + return authorService.getAuthorBooks(id); + } + } diff --git a/SpringApp/library/src/main/java/com/ip/library/authors/model/AuthorEntity.java b/SpringApp/library/src/main/java/com/ip/library/authors/model/AuthorEntity.java index 2420b69..9a96e32 100644 --- a/SpringApp/library/src/main/java/com/ip/library/authors/model/AuthorEntity.java +++ b/SpringApp/library/src/main/java/com/ip/library/authors/model/AuthorEntity.java @@ -1,11 +1,18 @@ package com.ip.library.authors.model; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; +import com.ip.library.books.model.BookEntity; import com.ip.library.core.model.BaseEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; import jakarta.persistence.Table; @Entity @@ -13,6 +20,13 @@ import jakarta.persistence.Table; public class AuthorEntity extends BaseEntity { @Column(nullable = false, unique = true, length = 20) private String name; + @ManyToMany(cascade = { CascadeType.ALL }) + @JoinTable( + name = "authors_books", + joinColumns = { @JoinColumn(name = "author_id", nullable = false) }, + inverseJoinColumns = { @JoinColumn(name = "book_id", nullable = false) } + ) + private Set books = new HashSet<>(); public AuthorEntity() { super(); @@ -30,6 +44,14 @@ public class AuthorEntity extends BaseEntity { this.name = name; } + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; + } + @Override public int hashCode() { return Objects.hash(id, name); @@ -42,8 +64,9 @@ public class AuthorEntity extends BaseEntity { if (obj == null || getClass() != obj.getClass()) return false; final AuthorEntity other = (AuthorEntity) obj; - return Objects.equals(other.getId(), id) - && Objects.equals(other.getName(), name); + return + Objects.equals(other.getId(), id) + && Objects.equals(other.getName(), name); } } diff --git a/SpringApp/library/src/main/java/com/ip/library/authors/repository/AuthorRepository.java b/SpringApp/library/src/main/java/com/ip/library/authors/repository/AuthorRepository.java index 0e8e9b6..5cd00a2 100644 --- a/SpringApp/library/src/main/java/com/ip/library/authors/repository/AuthorRepository.java +++ b/SpringApp/library/src/main/java/com/ip/library/authors/repository/AuthorRepository.java @@ -1,11 +1,24 @@ package com.ip.library.authors.repository; +import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import com.ip.library.authors.model.AuthorEntity; +import com.ip.library.books.model.BookEntity; -public interface AuthorRepository extends CrudRepository { +public interface AuthorRepository extends + CrudRepository, + PagingAndSortingRepository { Optional findByNameIgnoreCase(String name); + @Query( + "select a.books " + + "from AuthorEntity a, BookEntity b " + + "where a.id = ?1 " + + "order by b.id" + ) + List getAuthorBooks(Long authorId); } diff --git a/SpringApp/library/src/main/java/com/ip/library/authors/service/AuthorService.java b/SpringApp/library/src/main/java/com/ip/library/authors/service/AuthorService.java index 27eee8f..a93ffcf 100644 --- a/SpringApp/library/src/main/java/com/ip/library/authors/service/AuthorService.java +++ b/SpringApp/library/src/main/java/com/ip/library/authors/service/AuthorService.java @@ -9,6 +9,7 @@ import org.springframework.transaction.annotation.Transactional; import com.ip.library.core.error.NotFoundException; import com.ip.library.authors.model.AuthorEntity; import com.ip.library.authors.repository.AuthorRepository; +import com.ip.library.books.model.BookEntity; @Service public class AuthorService { @@ -63,4 +64,9 @@ public class AuthorService { repository.delete(existsEntity); return existsEntity; } + + @Transactional + public List getAuthorBooks(long authorId) { + return repository.getAuthorBooks(authorId); + } } diff --git a/SpringApp/library/src/main/java/com/ip/library/books/api/BookController.java b/SpringApp/library/src/main/java/com/ip/library/books/api/BookController.java index 34a278c..47f959b 100644 --- a/SpringApp/library/src/main/java/com/ip/library/books/api/BookController.java +++ b/SpringApp/library/src/main/java/com/ip/library/books/api/BookController.java @@ -22,16 +22,16 @@ import com.ip.library.authors.service.AuthorService; import jakarta.validation.Valid; @RestController -@RequestMapping(Constants.API_URL + "/books") +@RequestMapping(Constants.API_URL + "/book") public class BookController { - private final BookService itemService; + private final BookService bookService; private final TypeService typeService; private final AuthorService authorService; private final ModelMapper modelMapper; public BookController(BookService itemService, TypeService typeService, AuthorService authorService, ModelMapper modelMapper) { - this.itemService = itemService; + this.bookService = itemService; this.typeService = typeService; this.authorService = authorService; this.modelMapper = modelMapper; @@ -44,36 +44,40 @@ public class BookController { private BookEntity toEntity(BookDto dto) { final BookEntity entity = modelMapper.map(dto, BookEntity.class); entity.setType(typeService.get(dto.getTypeId())); - entity.setAuthor(authorService.get(dto.getAuthorId())); + entity.setAuthors(dto.getAuthorsId().stream().map(authorService::get).toList()); return entity; } @GetMapping public List getAll( - @RequestParam(name = "typeId", defaultValue = "-1") Long typeId, - @RequestParam(name = "authorId", defaultValue = "-1") Long authorId, - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { - return itemService.getAll(typeId, authorId).stream().map(this::toDto).toList(); + @RequestParam(name = "typeId", defaultValue = "-1") Long typeId, + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { + return bookService.getAll(typeId).stream().map(this::toDto).toList(); } @GetMapping("/{id}") public BookDto get(@PathVariable(name = "id") Long id) { - return toDto(itemService.get(id)); + return toDto(bookService.get(id)); } @PostMapping public BookDto create(@RequestBody @Valid BookDto dto) { - return toDto(itemService.create(toEntity(dto))); + return toDto(bookService.create(toEntity(dto))); } @PutMapping("/{id}") public BookDto update(@PathVariable(name = "id") Long id, @RequestBody BookDto dto) { - return toDto(itemService.update(id, toEntity(dto))); + return toDto(bookService.update(id, toEntity(dto))); } @DeleteMapping("/{id}") public BookDto delete(@PathVariable(name = "id") Long id) { - return toDto(itemService.delete(id)); + return toDto(bookService.delete(id)); } + + @GetMapping("/{bookId}/users/number") + public int getBookSubscribersNumber(@PathVariable(name = "bookId") Long bookId) { + return bookService.getBookSubscribersNumber(bookId); + } } diff --git a/SpringApp/library/src/main/java/com/ip/library/books/api/BookDto.java b/SpringApp/library/src/main/java/com/ip/library/books/api/BookDto.java index db6aad7..5c1ccd2 100644 --- a/SpringApp/library/src/main/java/com/ip/library/books/api/BookDto.java +++ b/SpringApp/library/src/main/java/com/ip/library/books/api/BookDto.java @@ -1,5 +1,7 @@ package com.ip.library.books.api; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.Min; @@ -15,8 +17,7 @@ public class BookDto { @Min(1) private Long typeId; @NotNull - @Min(1) - private Long authorId; + private List authorsId; public Long getId() { return id; @@ -34,11 +35,11 @@ public class BookDto { this.typeId = typeId; } - public Long getAuthorId() { - return authorId; + public List getAuthorsId() { + return authorsId; } - public void setAuthorId(Long authorId) { - this.authorId = authorId; + public void setAuthorId(List authorsId) { + this.authorsId = authorsId; } } diff --git a/SpringApp/library/src/main/java/com/ip/library/books/model/BookEntity.java b/SpringApp/library/src/main/java/com/ip/library/books/model/BookEntity.java index 01d7365..568288c 100644 --- a/SpringApp/library/src/main/java/com/ip/library/books/model/BookEntity.java +++ b/SpringApp/library/src/main/java/com/ip/library/books/model/BookEntity.java @@ -1,19 +1,22 @@ package com.ip.library.books.model; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import com.ip.library.core.model.BaseEntity; +import com.ip.library.favorites.model.FavoriteEntity; import com.ip.library.types.model.TypeEntity; -import com.ip.library.users.model.UserEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; import jakarta.persistence.Table; @@ -25,25 +28,26 @@ public class BookEntity extends BaseEntity { @Column(nullable = false, unique = true, length = 50) private String name; @ManyToOne - @JoinColumn(name = "typeId", nullable = false) + @JoinColumn(name = "type_id", nullable = false) @OrderBy("id ASC") private TypeEntity type; - @ManyToOne - @JoinColumn(name = "authorId", nullable = false) + @ManyToMany(mappedBy = "books") @OrderBy("id ASC") - private AuthorEntity author; - @ManyToMany(mappedBy = "books", fetch = FetchType.EAGER) - @OrderBy("id ASC") - private Set users = new HashSet<>(); + private List authors = new ArrayList<>(); public BookEntity() { super(); } - public BookEntity(String name, TypeEntity type, AuthorEntity author) { + public BookEntity(String name, TypeEntity type) { this.name = name; this.type = type; - this.author = author; + } + + public BookEntity(String name, TypeEntity type, List authors) { + this.name = name; + this.type = type; + this.authors = authors; } public String getName() { @@ -62,41 +66,17 @@ public class BookEntity extends BaseEntity { this.type = type; } - public AuthorEntity getAuthor() { - return author; + public List getAuthors() { + return authors; } - public void setAuthor(AuthorEntity author) { - this.author = author; - } - - public Set getUsers() { - return users; - } - - public void setUsers(Set users) { - this.users = users; - } - - public UserEntity addUser(UserEntity user) { - users.add(user); - if (!user.getBooks().contains(this)){ - user.getBooks().add(this); - } - return user; - } - - public UserEntity removeUser(UserEntity user) { - users.remove(user); - if (user.getBooks().contains(this)) { - user.getBooks().remove(this); - } - return user; + public void setAuthors(List authors) { + this.authors = authors; } @Override public int hashCode() { - return Objects.hash(id, type, author); + return Objects.hash(id, name, type, authors); } @Override @@ -106,10 +86,10 @@ public class BookEntity extends BaseEntity { if (obj == null || getClass() != obj.getClass()) return false; final BookEntity other = (BookEntity) obj; - return Objects.equals(other.getId(), id) - && Objects.equals(other.getName(), name) - && Objects.equals(other.getType(), type) - && Objects.equals(other.getAuthor(), author) - && Objects.equals(other.getUsers(), users); + return + Objects.equals(other.getId(), id) + && Objects.equals(other.getName(), name) + && Objects.equals(other.getType(), type) + && Objects.equals(other.getAuthors(), authors); } } diff --git a/SpringApp/library/src/main/java/com/ip/library/books/repository/BookRepository.java b/SpringApp/library/src/main/java/com/ip/library/books/repository/BookRepository.java index 7a8768f..95b8603 100644 --- a/SpringApp/library/src/main/java/com/ip/library/books/repository/BookRepository.java +++ b/SpringApp/library/src/main/java/com/ip/library/books/repository/BookRepository.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; @@ -13,10 +14,14 @@ public interface BookRepository extends CrudRepository, PagingAndSortingRepository { Optional findByNameIgnoreCase(String name); + List findByTypeId(long typeId); + List findByTypeId(long typeId, Pageable pageable); - List findByAuthorId(long authorId); - List findByAuthorId(long authorId, Pageable pageable); - List findByTypeIdAndAuthorId(long typeId, long authorId); - List findByTypeIdAndAuthorId(long typeId, long authorId, Pageable pageable); + + @Query( + "select count(*) as number " + + "from FavoriteEntity f " + + "where f.book.id = ?1") + int getBookSubscribersNumber(long bookId); } diff --git a/SpringApp/library/src/main/java/com/ip/library/books/service/BookService.java b/SpringApp/library/src/main/java/com/ip/library/books/service/BookService.java index a658788..0557de8 100644 --- a/SpringApp/library/src/main/java/com/ip/library/books/service/BookService.java +++ b/SpringApp/library/src/main/java/com/ip/library/books/service/BookService.java @@ -33,32 +33,22 @@ public class BookService { } @Transactional(readOnly = true) - public List getAll(long typeId, long authorId) { - if (typeId <= 0L && authorId <= 0L) { + public List getAll(long typeId) { + if (typeId <= 0L) { return getAll(); } - if (typeId <= 0L){ - return repository.findByAuthorId(authorId); - } - if (authorId <= 0L){ - return repository.findByTypeId(typeId); - } - return repository.findByTypeIdAndAuthorId(typeId, authorId); + return repository.findByTypeId(typeId); } @Transactional(readOnly = true) - public List getAll(long typeId, long authorId, int page, int size) { + public List getAll(long typeId, int page, int size) { PageRequest pageRequest = PageRequest.of(page, size); - if (typeId <= 0L && authorId <= 0L) { - return StreamSupport.stream(repository.findAll(pageRequest).spliterator(), false).toList(); + if (typeId <= 0L) { + return StreamSupport.stream( + repository.findAll(pageRequest).spliterator(), false + ).toList(); } - if (typeId <= 0L){ - return repository.findByAuthorId(authorId, pageRequest); - } - if (authorId <= 0L){ - return repository.findByTypeId(typeId, pageRequest); - } - return repository.findByTypeIdAndAuthorId(typeId, authorId, pageRequest); + return repository.findByTypeId(typeId, pageRequest); } @Transactional(readOnly = true) @@ -85,7 +75,7 @@ public class BookService { checkNameUniqueness(entity.getName()); existsEntity.setName(entity.getName()); existsEntity.setType(entity.getType()); - existsEntity.setAuthor(entity.getAuthor()); + existsEntity.setAuthors(entity.getAuthors()); return repository.save(existsEntity); } @@ -95,4 +85,9 @@ public class BookService { repository.delete(existsEntity); return existsEntity; } + + @Transactional(readOnly = true) + public int getBookSubscribersNumber(long bookId) { + return repository.getBookSubscribersNumber(bookId); + } } diff --git a/SpringApp/library/src/main/java/com/ip/library/favorites/api/FavoriteController.java b/SpringApp/library/src/main/java/com/ip/library/favorites/api/FavoriteController.java deleted file mode 100644 index 317a931..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/favorites/api/FavoriteController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.ip.library.favorites.api; - -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -import org.modelmapper.ModelMapper; -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.RequestMapping; - -import com.ip.library.books.api.BookDto; -import com.ip.library.books.model.BookEntity; -import com.ip.library.core.configuration.Constants; -import com.ip.library.favorites.service.FavoriteService; - - -@RestController -@RequestMapping(Constants.API_URL + "/favorities") -public class FavoriteController { - private FavoriteService favoriteService; - private final ModelMapper modelMapper; - - public FavoriteController (FavoriteService favorityService, ModelMapper modelMapper) { - this.favoriteService = favorityService; - this.modelMapper = modelMapper; - } - private BookDto toBookDto (BookEntity entity) { - return modelMapper.map(entity, BookDto.class); - } - - @GetMapping("/user/{userId}/books/{bookId}") - public boolean addFavorite( - @PathVariable(name = "userId") Long userId, - @PathVariable(name = "bookId") Long bookId) { - return favoriteService.addFavorite(userId, bookId); - } - - @DeleteMapping("/user/{userId}/books/{bookId}") - public boolean removeFavorite( - @PathVariable(name = "userId") Long userId, - @PathVariable(name = "bookId") Long bookId) { - return favoriteService.removeFavorite(userId, bookId); - } - - @GetMapping("/user/{userId}/books") - public List getUserFavorites(@PathVariable(name = "userId") Long userId) { - return favoriteService.getUserFavorities(userId).stream().map(this::toBookDto).toList(); - } - - @GetMapping("/book/{bookId}/users/number") - public int getBookSubscribersNumber(@PathVariable(name = "bookId") Long bookId) { - return favoriteService.getBookSubscribersNumber(bookId); - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/favorites/model/FavoriteEntity.java b/SpringApp/library/src/main/java/com/ip/library/favorites/model/FavoriteEntity.java new file mode 100644 index 0000000..e3acc71 --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/favorites/model/FavoriteEntity.java @@ -0,0 +1,79 @@ +package com.ip.library.favorites.model; + +import java.util.Objects; + +import com.ip.library.books.model.BookEntity; +import com.ip.library.users.model.UserEntity; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.Table; + +@Entity +@Table(name = "favorites") +public class FavoriteEntity { + @EmbeddedId + private UserBookId id = new UserBookId(); + @ManyToOne + @MapsId("userId") + @JoinColumn(name = "user_id") + private UserEntity user; + @ManyToOne + @MapsId("bookId") + @JoinColumn(name = "book_id") + private BookEntity book; + + public FavoriteEntity() {} + + public FavoriteEntity(UserEntity user, BookEntity book) { + this.user = user; + this.book = book; + } + + public void setId(UserBookId id) { + this.id = id; + } + + public UserBookId getId() { + return id; + } + + public void setUser(UserEntity user) { + this.user = user; + if (!user.getFavorites().contains(this)) + user.getFavorites().add(this); + } + + public UserEntity getUser() { + return user; + } + + public void setBook(BookEntity book) { + this.book = book; + } + + public BookEntity getBook() { + return book; + } + + @Override + public int hashCode() { + return Objects.hash(id, user.getId(), book.getId()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + final FavoriteEntity other = (FavoriteEntity) obj; + return + Objects.equals(other.getId(), id) && + Objects.equals(other.getUser().getId(), user.getId()) && + Objects.equals(other.getBook().getId(), book.getId()); + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/favorites/model/UserBookId.java b/SpringApp/library/src/main/java/com/ip/library/favorites/model/UserBookId.java new file mode 100644 index 0000000..0a39979 --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/favorites/model/UserBookId.java @@ -0,0 +1,55 @@ +package com.ip.library.favorites.model; + +import java.util.Objects; +import java.util.Optional; + +import jakarta.persistence.Embeddable; + +@Embeddable +public class UserBookId { + private Long userId; + private Long bookId; + + public UserBookId() {} + + public UserBookId(Long userId, Long bookId) { + this.userId = userId; + this.bookId = bookId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getUserId() { + return userId; + } + + public void setBookId(Long bookId) { + this.bookId = bookId; + } + + public Long getBookId() { + return bookId; + } + + @Override + public int hashCode() { + return Objects.hash( + Optional.ofNullable(userId).orElse(-1L), + Optional.ofNullable(bookId).orElse(-1L) + ); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + UserBookId other = (UserBookId) obj; + return + Objects.equals(other.userId, userId) && + Objects.equals(other.bookId, bookId); + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/favorites/service/FavoriteService.java b/SpringApp/library/src/main/java/com/ip/library/favorites/service/FavoriteService.java deleted file mode 100644 index 09682be..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/favorites/service/FavoriteService.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.ip.library.favorites.service; - -import java.util.Set; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.ip.library.books.model.BookEntity; -import com.ip.library.books.service.BookService; -import com.ip.library.users.model.UserEntity; -import com.ip.library.users.service.UserService; -@Service -public class FavoriteService { - BookService bookService; - UserService userService; - - public FavoriteService (BookService bookService, UserService userService) { - this.bookService = bookService; - this.userService = userService; - } - - @Transactional - public boolean addFavorite(long userId, long bookId) { - final UserEntity existsUser = userService.get(userId); - final BookEntity book = bookService.get(bookId); - existsUser.addBook(book); - return true; - } - - @Transactional - public boolean removeFavorite(long userId, long bookId) { - final UserEntity existsUser = userService.get(userId); - final BookEntity book = bookService.get(bookId); - existsUser.removeBook(book); - return true; - } - - @Transactional(readOnly = true) - public Set getUserFavorities (long userId) { - return userService.get(userId).getBooks(); - } - - @Transactional(readOnly = true) - public int getBookSubscribersNumber(long bookId) { - return bookService.get(bookId).getUsers().size(); - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/types/model/TypeEntity.java b/SpringApp/library/src/main/java/com/ip/library/types/model/TypeEntity.java index 9bd403d..61d97e6 100644 --- a/SpringApp/library/src/main/java/com/ip/library/types/model/TypeEntity.java +++ b/SpringApp/library/src/main/java/com/ip/library/types/model/TypeEntity.java @@ -42,8 +42,9 @@ public class TypeEntity extends BaseEntity { if (obj == null || getClass() != obj.getClass()) return false; final TypeEntity other = (TypeEntity) obj; - return Objects.equals(other.getId(), id) - && Objects.equals(other.getName(), name); + return + Objects.equals(other.getId(), id) + && Objects.equals(other.getName(), name); } } diff --git a/SpringApp/library/src/main/java/com/ip/library/users/api/UserController.java b/SpringApp/library/src/main/java/com/ip/library/users/api/UserController.java index 62dc82c..95696dd 100644 --- a/SpringApp/library/src/main/java/com/ip/library/users/api/UserController.java +++ b/SpringApp/library/src/main/java/com/ip/library/users/api/UserController.java @@ -1,5 +1,7 @@ package com.ip.library.users.api; +import java.util.List; + import org.modelmapper.ModelMapper; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -11,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import com.ip.library.books.api.BookDto; +import com.ip.library.books.model.BookEntity; import com.ip.library.core.api.PageDto; import com.ip.library.core.api.PageDtoMapper; import com.ip.library.core.configuration.Constants; @@ -30,11 +34,14 @@ public class UserController { this.modelMapper = modelMapper; } - private UserDto toDto(UserEntity entity) { + private UserDto toUserDto(UserEntity entity) { return modelMapper.map(entity, UserDto.class); } + private BookDto toBookDto (BookEntity entity) { + return modelMapper.map(entity, BookDto.class); + } - private UserEntity toEntity(UserDto dto) { + private UserEntity toUserEntity(UserDto dto) { return modelMapper.map(dto, UserEntity.class); } @@ -42,31 +49,50 @@ public class UserController { public PageDto getAll( @RequestParam(name = "page", defaultValue = "0") int page, @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { - return PageDtoMapper.toDto(userService.getAll(page, size), this::toDto); + return PageDtoMapper.toDto(userService.getAll(page, size), this::toUserDto); } @GetMapping("/{id}") public UserDto get(@PathVariable(name = "id") Long id) { - return toDto(userService.get(id)); + return toUserDto(userService.get(id)); } @PostMapping public UserDto create(@RequestBody @Valid UserDto dto) { - return toDto(userService.create(toEntity(dto))); + return toUserDto(userService.create(toUserEntity(dto))); } @PutMapping("/{id}") public UserDto update(@PathVariable(name = "id") Long id, @RequestBody UserDto dto) { - return toDto(userService.update(id, toEntity(dto))); + return toUserDto(userService.update(id, toUserEntity(dto))); } @DeleteMapping("/{id}") public UserDto delete(@PathVariable(name = "id") Long id) { - return toDto(userService.delete(id)); + return toUserDto(userService.delete(id)); } @PutMapping("/password/{id}") public UserDto changePassword(@PathVariable(name = "id") Long id, @RequestBody String newPassword) { - return toDto(userService.changePassword(id, newPassword)); + return toUserDto(userService.changePassword(id, newPassword)); } + + @DeleteMapping("/{userId}/books/{bookId}") + public boolean removeFavorite( + @PathVariable(name = "userId") Long userId, + @PathVariable(name = "bookId") Long bookId) { + return userService.removeFavorite(userId, bookId); + } + + @GetMapping("/{userId}/books/{bookId}") + public boolean addFavorite( + @PathVariable(name = "userId") Long userId, + @PathVariable(name = "bookId") Long bookId) { + return userService.addFavorite(userId, bookId); + } + + @GetMapping("/{userId}/books") + public List getUserFavorites(@PathVariable(name = "userId") Long userId) { + return userService.getUserFavorities(userId).stream().map(this::toBookDto).toList(); + } } diff --git a/SpringApp/library/src/main/java/com/ip/library/users/model/UserEntity.java b/SpringApp/library/src/main/java/com/ip/library/users/model/UserEntity.java index ef2e729..81471d1 100644 --- a/SpringApp/library/src/main/java/com/ip/library/users/model/UserEntity.java +++ b/SpringApp/library/src/main/java/com/ip/library/users/model/UserEntity.java @@ -6,14 +6,13 @@ import java.util.Set; import com.ip.library.books.model.BookEntity; import com.ip.library.core.model.BaseEntity; +import com.ip.library.favorites.model.FavoriteEntity; +import com.ip.library.favorites.model.UserBookId; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; import jakarta.persistence.Table; @@ -26,14 +25,9 @@ public class UserEntity extends BaseEntity { private String password; @Column(nullable = false, unique = false, length = 20) private String role = "user"; - @ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER) - @JoinTable( - name = "favorities", - joinColumns = { @JoinColumn(name = "userId", nullable = false) }, - inverseJoinColumns = { @JoinColumn(name = "bookId", nullable = false) } - ) + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @OrderBy("id ASC") - private Set books = new HashSet<>(); + private Set favorites = new HashSet<>(); public UserEntity() { super(); @@ -72,28 +66,20 @@ public class UserEntity extends BaseEntity { this.role = role; } - public Set getBooks() { - return books; + public Set getFavorites() { + return favorites; } - public void setBooks(Set books) { - this.books = books; + public void setFavorites(Set favorites) { + this.favorites = favorites; } - public BookEntity addBook(BookEntity book) { - books.add(book); - if (!book.getUsers().contains(this)) { - book.getUsers().add(this); - } - return book; + public boolean addBook(BookEntity book) { + return favorites.add(new FavoriteEntity(this, book)); } - public BookEntity removeBook(BookEntity book) { - books.remove(book); - if (book.getUsers().contains(this)) { - book.getUsers().remove(this); - } - return book; + public boolean removeBook(BookEntity book) { + return favorites.remove(new FavoriteEntity(this, book)); } @Override @@ -108,10 +94,10 @@ public class UserEntity extends BaseEntity { if (obj == null || getClass() != obj.getClass()) return false; final UserEntity other = (UserEntity) obj; - return Objects.equals(other.getId(), id) - && Objects.equals(other.getLogin(), login) - && Objects.equals(other.getPassword(), password) - && Objects.equals(other.getRole(), role) - && Objects.equals(other.getBooks(), books); + return + Objects.equals(other.getId(), id) + && Objects.equals(other.getLogin(), login) + && Objects.equals(other.getPassword(), password) + && Objects.equals(other.getRole(), role); } } diff --git a/SpringApp/library/src/main/java/com/ip/library/users/repository/UserRepository.java b/SpringApp/library/src/main/java/com/ip/library/users/repository/UserRepository.java index 73a6bc1..47592c2 100644 --- a/SpringApp/library/src/main/java/com/ip/library/users/repository/UserRepository.java +++ b/SpringApp/library/src/main/java/com/ip/library/users/repository/UserRepository.java @@ -1,14 +1,24 @@ package com.ip.library.users.repository; +import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; +import com.ip.library.books.model.BookEntity; import com.ip.library.users.model.UserEntity; public interface UserRepository extends CrudRepository, PagingAndSortingRepository { Optional findByLoginIgnoreCase(String login); + + @Query( + "select f.book " + + "from FavoriteEntity f " + + "where f.user.id = ?1 " + + "order by f.book.id") + public List getUserFavorities(Long userId); } diff --git a/SpringApp/library/src/main/java/com/ip/library/users/service/UserService.java b/SpringApp/library/src/main/java/com/ip/library/users/service/UserService.java index 7ea2993..c035414 100644 --- a/SpringApp/library/src/main/java/com/ip/library/users/service/UserService.java +++ b/SpringApp/library/src/main/java/com/ip/library/users/service/UserService.java @@ -8,6 +8,8 @@ import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.ip.library.books.model.BookEntity; +import com.ip.library.books.service.BookService; import com.ip.library.core.error.NotFoundException; import com.ip.library.users.model.UserEntity; import com.ip.library.users.repository.UserRepository; @@ -15,9 +17,11 @@ import com.ip.library.users.repository.UserRepository; @Service public class UserService { private final UserRepository repository; + private final BookService bookService; - public UserService(UserRepository repository) { + public UserService(UserRepository repository, BookService bookService) { this.repository = repository; + this.bookService = bookService; } private void checkLoginUniqueness(String name){ @@ -85,4 +89,23 @@ public class UserService { existsEntity.setPassword(newPassword); return repository.save(existsEntity); } + + @Transactional + public boolean addFavorite(long userId, long bookId) { + final UserEntity existsUser = get(userId); + final BookEntity book = bookService.get(bookId); + return existsUser.addBook(book); + } + + @Transactional + public boolean removeFavorite(long userId, long bookId) { + final UserEntity existsUser = get(userId); + final BookEntity book = bookService.get(bookId); + return existsUser.removeBook(book); + } + + @Transactional(readOnly = true) + public List getUserFavorities (long userId) { + return repository.getUserFavorities(userId); + } } diff --git a/SpringApp/library/src/test/java/com/ip/library/BooksTests.java b/SpringApp/library/src/test/java/com/ip/library/BooksTests.java index 48df6fe..a95b233 100644 --- a/SpringApp/library/src/test/java/com/ip/library/BooksTests.java +++ b/SpringApp/library/src/test/java/com/ip/library/BooksTests.java @@ -1,5 +1,8 @@ package com.ip.library; +import java.util.List; +import java.util.ArrayList; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -26,7 +29,8 @@ class BooksTests { private AuthorService authorService; private BookEntity book; private TypeEntity type; - private AuthorEntity author; + private AuthorEntity author1; + private AuthorEntity author2; @AfterEach void removeData() { @@ -40,11 +44,14 @@ class BooksTests { removeData(); type = typeService.create(new TypeEntity("type1")); var type2 = typeService.create(new TypeEntity("type2")); - author = authorService.create(new AuthorEntity("author1")); - var author2 = authorService.create(new AuthorEntity("author2")); - bookService.create(new BookEntity("book1", type, author2)); - bookService.create(new BookEntity("book2", type2, author)); - book = bookService.create(new BookEntity("book3", type, author)); + author1 = authorService.create(new AuthorEntity("author1")); + author2 = authorService.create(new AuthorEntity("author2")); + List list1 = new ArrayList<>(); + list1.add(author1); + list1.add(author2); + bookService.create(new BookEntity("book1", type)); + bookService.create(new BookEntity("book2", type2)); + book = bookService.create(new BookEntity("book3", type, list1)); } @Test @@ -52,8 +59,9 @@ class BooksTests { Assertions.assertEquals(3, bookService.getAll().size()); Assertions.assertEquals("book3", book.getName()); Assertions.assertEquals(type, book.getType()); - Assertions.assertEquals(author, book.getAuthor()); - Assertions.assertEquals(0, book.getUsers().size()); + Assertions.assertTrue(book.getAuthors().contains(author1)); + Assertions.assertTrue(book.getAuthors().contains(author2)); + Assertions.assertEquals(0, bookService.getBookSubscribersNumber(book.getId())); } @Test @@ -67,14 +75,10 @@ class BooksTests { final String testName = book.getName() + "TEST"; final TypeEntity testType = typeService.create( new TypeEntity(book.getType().getName() + "TEST")); - final AuthorEntity testAuthor = authorService.create( - new AuthorEntity(book.getAuthor().getName() + "TEST")); - book = bookService.update(book.getId(), new BookEntity( - testName, testType, testAuthor)); + book = bookService.update(book.getId(), new BookEntity(testName, testType)); Assertions.assertEquals(3, bookService.getAll().size()); Assertions.assertEquals(testName, book.getName()); Assertions.assertEquals(testType, book.getType()); - Assertions.assertEquals(testAuthor, book.getAuthor()); } @Test @@ -82,7 +86,7 @@ class BooksTests { bookService.delete(book.getId()); Assertions.assertEquals(2, bookService.getAll().size()); final BookEntity newEntity = bookService.create( - new BookEntity(book.getName(), book.getType(), book.getAuthor())); + new BookEntity(book.getName(), book.getType(), book.getAuthors())); Assertions.assertEquals(3, bookService.getAll().size()); Assertions.assertNotEquals(book.getId(), newEntity.getId()); } @@ -91,11 +95,11 @@ class BooksTests { void nullNameTest() { Assertions.assertThrows( DataIntegrityViolationException.class, - () -> bookService.create(new BookEntity(null, book.getType(), book.getAuthor())) + () -> bookService.create(new BookEntity(null, book.getType(), book.getAuthors())) ); Assertions.assertThrows( DataIntegrityViolationException.class, - () -> bookService.update(book.getId(), new BookEntity(null, book.getType(), book.getAuthor())) + () -> bookService.update(book.getId(), new BookEntity(null, book.getType(), book.getAuthors())) ); } @@ -103,23 +107,11 @@ class BooksTests { void nullTypeTest() { Assertions.assertThrows( DataIntegrityViolationException.class, - () -> bookService.create(new BookEntity(book.getName() + "TEST", null, book.getAuthor())) + () -> bookService.create(new BookEntity(book.getName() + "TEST", null, book.getAuthors())) ); Assertions.assertThrows( DataIntegrityViolationException.class, - () -> bookService.update(book.getId(), new BookEntity(book.getName() + "TEST", null, book.getAuthor())) - ); - } - - @Test - void nullAuthorTest() { - Assertions.assertThrows( - DataIntegrityViolationException.class, - () -> bookService.create(new BookEntity(book.getName() + "TEST", book.getType(), null)) - ); - Assertions.assertThrows( - DataIntegrityViolationException.class, - () -> bookService.update(book.getId(), new BookEntity(book.getName() + "TEST", book.getType(), null)) + () -> bookService.update(book.getId(), new BookEntity(book.getName() + "TEST", null, book.getAuthors())) ); } @@ -127,11 +119,11 @@ class BooksTests { void uniqueNameTest() { Assertions.assertThrows( IllegalArgumentException.class, - () -> bookService.create(new BookEntity(book.getName(), book.getType(), book.getAuthor())) + () -> bookService.create(new BookEntity(book.getName(), book.getType(), book.getAuthors())) ); Assertions.assertThrows( IllegalArgumentException.class, - () -> bookService.update(book.getId(), new BookEntity(book.getName(), book.getType(), book.getAuthor())) + () -> bookService.update(book.getId(), new BookEntity(book.getName(), book.getType(), book.getAuthors())) ); } } diff --git a/SpringApp/library/src/test/java/com/ip/library/FavoritesTests.java b/SpringApp/library/src/test/java/com/ip/library/FavoritesTests.java index 11a8bb6..f3a73a2 100644 --- a/SpringApp/library/src/test/java/com/ip/library/FavoritesTests.java +++ b/SpringApp/library/src/test/java/com/ip/library/FavoritesTests.java @@ -7,11 +7,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import com.ip.library.authors.model.AuthorEntity; -import com.ip.library.authors.service.AuthorService; import com.ip.library.books.model.BookEntity; import com.ip.library.books.service.BookService; -import com.ip.library.favorites.service.FavoriteService; import com.ip.library.types.model.TypeEntity; import com.ip.library.types.service.TypeService; import com.ip.library.users.model.UserEntity; @@ -19,16 +16,12 @@ import com.ip.library.users.service.UserService; @SpringBootTest class FavoritesTests { - @Autowired - private FavoriteService favorityService; @Autowired private BookService bookService; @Autowired private UserService userService; @Autowired private TypeService typeService; - @Autowired - private AuthorService authorService; private UserEntity user; private BookEntity book1; private BookEntity book2; @@ -36,7 +29,6 @@ class FavoritesTests { @AfterEach void removeData() { bookService.getAll().forEach(item -> bookService.delete(item.getId())); - authorService.getAll().forEach(item -> authorService.delete(item.getId())); typeService.getAll().forEach(item -> typeService.delete(item.getId())); userService.getAll().forEach(item -> userService.delete(item.getId())); } @@ -45,38 +37,41 @@ class FavoritesTests { void createData() { removeData(); TypeEntity type = typeService.create(new TypeEntity("type1")); - AuthorEntity author = authorService.create(new AuthorEntity("author1")); - book1 = bookService.create(new BookEntity("book1", type, author)); - book2 = bookService.create(new BookEntity("book2", type, author)); + book1 = bookService.create(new BookEntity("book1", type)); + book2 = bookService.create(new BookEntity("book2", type)); userService.create(new UserEntity("user1", "123")); userService.create(new UserEntity("user2", "456")); user = userService.create(new UserEntity("user3", "aqw2sed45")); } @Test - void addAndRemoveTest() { - Assertions.assertEquals(0, user.getBooks().size()); - Assertions.assertEquals(0, book1.getUsers().size()); - Assertions.assertEquals(0, book2.getUsers().size()); - favorityService.addFavorite(user.getId(), book1.getId()); + void favoritesTest() { + Assertions.assertEquals(0, userService.getUserFavorities(user.getId()).size()); + Assertions.assertEquals(0, bookService.getBookSubscribersNumber(book1.getId())); + Assertions.assertEquals(0, bookService.getBookSubscribersNumber(book2.getId())); + + userService.addFavorite(user.getId(), book1.getId()); user = userService.get(user.getId()); book1 = bookService.get(book1.getId()); - Assertions.assertEquals(1, user.getBooks().size()); - Assertions.assertEquals(1, book1.getUsers().size()); - favorityService.addFavorite(user.getId(), book2.getId()); + Assertions.assertEquals(1, userService.getUserFavorities(user.getId()).size()); + Assertions.assertEquals(1, bookService.getBookSubscribersNumber(book1.getId())); + + userService.addFavorite(user.getId(), book2.getId()); user = userService.get(user.getId()); book2 = bookService.get(book2.getId()); - Assertions.assertEquals(2, user.getBooks().size()); - Assertions.assertEquals(1, book2.getUsers().size()); - favorityService.removeFavorite(user.getId(), book1.getId()); + Assertions.assertEquals(2, userService.getUserFavorities(user.getId()).size()); + Assertions.assertEquals(1, bookService.getBookSubscribersNumber(book2.getId())); + + userService.removeFavorite(user.getId(), book1.getId()); user = userService.get(user.getId()); book1 = bookService.get(book1.getId()); - Assertions.assertEquals(1, user.getBooks().size()); - Assertions.assertEquals(0, book1.getUsers().size()); - favorityService.removeFavorite(user.getId(), book2.getId()); + Assertions.assertEquals(1, userService.getUserFavorities(user.getId()).size()); + Assertions.assertEquals(0, bookService.getBookSubscribersNumber(book1.getId())); + + userService.removeFavorite(user.getId(), book2.getId()); user = userService.get(user.getId()); book2 = bookService.get(book2.getId()); - Assertions.assertEquals(0, user.getBooks().size()); - Assertions.assertEquals(0, book2.getUsers().size()); + Assertions.assertEquals(0, userService.getUserFavorities(user.getId()).size()); + Assertions.assertEquals(0, bookService.getBookSubscribersNumber(book2.getId())); } } diff --git a/SpringApp/library/src/test/java/com/ip/library/UsersTests.java b/SpringApp/library/src/test/java/com/ip/library/UsersTests.java index 12a6f52..77d86a1 100644 --- a/SpringApp/library/src/test/java/com/ip/library/UsersTests.java +++ b/SpringApp/library/src/test/java/com/ip/library/UsersTests.java @@ -37,7 +37,7 @@ class UsersTests { Assertions.assertEquals("user3", user.getLogin()); Assertions.assertEquals("aqw2sed45", user.getPassword()); Assertions.assertEquals("user", user.getRole()); - Assertions.assertEquals(0, user.getBooks().size()); + Assertions.assertEquals(0, user.getFavorites().size()); } @Test @@ -57,7 +57,7 @@ class UsersTests { Assertions.assertEquals(oldId, user.getId()); Assertions.assertEquals(testName, user.getLogin()); Assertions.assertNotEquals(testPassword, user.getPassword()); - Assertions.assertEquals(0, user.getBooks().size()); + Assertions.assertEquals(0, userService.getUserFavorities(user.getId()).size()); } @Test