diff --git a/SpringApp/.vscode/launch.json b/SpringApp/.vscode/launch.json index 323cbac..b1e2afa 100644 --- a/SpringApp/.vscode/launch.json +++ b/SpringApp/.vscode/launch.json @@ -7,7 +7,7 @@ "cwd": "${workspaceFolder}", "mainClass": "com.ip.library.LibraryApplication", "projectName": "library", - "args": "", + "args": "--populate", "envFile": "${workspaceFolder}/.env" } ] diff --git a/SpringApp/library/build.gradle b/SpringApp/library/build.gradle index cd71bb9..dd32415 100644 --- a/SpringApp/library/build.gradle +++ b/SpringApp/library/build.gradle @@ -1,12 +1,23 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.2.2' + id 'org.springframework.boot' version '3.2.4' id 'io.spring.dependency-management' version '1.1.4' } group = 'com.ip' version = '0.0.1-SNAPSHOT' +defaultTasks 'bootRun' + +jar { + enabled = false +} + +bootJar { + archiveFileName = String.format('%s-%s.jar', rootProject.name, version) +} + +assert System.properties['java.specification.version'] == '17' || '21' java { sourceCompatibility = '17' } @@ -17,10 +28,12 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'org.modelmapper:modelmapper:3.2.0' - - developmentOnly 'org.springframework.boot:spring-boot-devtools' + + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'com.h2database:h2:2.2.224' testImplementation 'org.springframework.boot:spring-boot-starter-test' } 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 323416a..ab43fc5 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; @@ -43,24 +48,37 @@ public class LibraryApplication implements CommandLineRunner { public void run(String... args) throws Exception { if (args.length > 0 && args[0].equals("--populate")) { log.info("Create default types values"); - final var type1 = typeService.create(new TypeEntity(null, "type1")); - final var type2 = typeService.create(new TypeEntity(null, "type2")); + final var type1 = typeService.create(new TypeEntity("type1")); + final var type2 = typeService.create(new TypeEntity("type2")); log.info("Create default authors values"); - final var author1 = authorService.create(new AuthorEntity(null, "author1")); - final var author2 = authorService.create(new AuthorEntity(null, "author2")); + final var author1 = authorService.create(new AuthorEntity("author1")); + final var author2 = authorService.create(new AuthorEntity("author2")); log.info("Create default books values"); - final var book1 = bookService.create(new BookEntity(null, "book1", type1, author1)); - final var book2 = bookService.create(new BookEntity(null, "book2", type1, author2)); - final var book3 = bookService.create(new BookEntity(null, "book3", type2, author1)); - final var book4 = bookService.create(new BookEntity(null, "book4", type2, author2)); + final var book1 = bookService.create(new BookEntity("book1", type1)); + final var book2 = bookService.create(new BookEntity("book2", type1)); + final var book3 = bookService.create(new BookEntity("book3", type2)); + final var book4 = bookService.create(new BookEntity("book4", type2)); + + bookService.addAuthor(author1.getId(), book1.getId()); + bookService.addAuthor(author2.getId(), book2.getId()); + bookService.addAuthor(author1.getId(), book3.getId()); + bookService.addAuthor(author2.getId(), book3.getId()); log.info("Create default users values"); - final var user1 = userService.create(new UserEntity(null, "user1", "123", null, null)); - final var user2 = userService.create(new UserEntity(null, "user2", "123", null, null )); - final var admin1 = userService.create(new UserEntity(null, "admin1", "123", null, null)); + 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..31cf660 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 @@ -18,6 +18,7 @@ import com.ip.library.authors.service.AuthorService; import jakarta.validation.Valid; + @RestController @RequestMapping(Constants.API_URL + "/author") public class AuthorController { @@ -60,5 +61,5 @@ public class AuthorController { @DeleteMapping("/{id}") public AuthorDto delete(@PathVariable(name = "id") Long id) { return toDto(authorService.delete(id)); - } + } } diff --git a/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorDto.java b/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorDto.java index 4601838..59fb8c3 100644 --- a/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorDto.java +++ b/SpringApp/library/src/main/java/com/ip/library/authors/api/AuthorDto.java @@ -3,13 +3,15 @@ package com.ip.library.authors.api; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; public class AuthorDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotBlank + @Size(min = 5, max = 20) private String name; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) public Long getId() { return 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 9f6df0a..1add767 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,18 +1,34 @@ package com.ip.library.authors.model; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; +import com.ip.library.authors_books.model.AuthorsBooksEntity; +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.OneToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.Table; + +@Entity +@Table(name = "authors") public class AuthorEntity extends BaseEntity { + @Column(nullable = false, unique = true, length = 20) private String name; + @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true) + @OrderBy("id ASC") + private Set authorsBooks = new HashSet<>(); public AuthorEntity() { super(); } - public AuthorEntity(Long id, String name) { - super(id); + public AuthorEntity(String name) { this.name = name; } @@ -24,6 +40,22 @@ public class AuthorEntity extends BaseEntity { this.name = name; } + public Set getAuthorsBooks() { + return authorsBooks; + } + + public void setBooks(Set authorsBooks) { + this.authorsBooks = authorsBooks; + } + + public boolean addBook(BookEntity book) { + AuthorsBooksEntity entity = new AuthorsBooksEntity(this, book); + boolean result = authorsBooks.add(entity); + if (!book.getAuthorsBooks().contains(entity)) + book.getAuthorsBooks().add(entity); + return result; + } + @Override public int hashCode() { return Objects.hash(id, name); @@ -36,8 +68,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 f8c04fd..13c44dd 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,10 +1,14 @@ package com.ip.library.authors.repository; -import org.springframework.stereotype.Repository; +import java.util.Optional; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import com.ip.library.authors.model.AuthorEntity; -import com.ip.library.core.repository.MapRepository; -@Repository -public class AuthorRepository extends MapRepository { +public interface AuthorRepository extends + CrudRepository, + PagingAndSortingRepository { + Optional findByNameIgnoreCase(String name); } 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 cc616d3..27eee8f 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 @@ -1,9 +1,10 @@ package com.ip.library.authors.service; import java.util.List; -import java.util.Optional; +import java.util.stream.StreamSupport; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.ip.library.core.error.NotFoundException; import com.ip.library.authors.model.AuthorEntity; @@ -17,27 +18,49 @@ public class AuthorService { this.repository = repository; } + private void checkNameUniqueness(String name) { + if (repository.findByNameIgnoreCase(name).isPresent()) { + throw new IllegalArgumentException( + String.format("Author with name %s already exists", name) + ); + } + } + + @Transactional(readOnly = true) public List getAll() { - return repository.getAll(); + return StreamSupport.stream(repository.findAll().spliterator(), false).toList(); } - public AuthorEntity get(Long id) { - return Optional.ofNullable(repository.get(id)) - .orElseThrow(() -> new NotFoundException(id)); + @Transactional(readOnly = true) + public AuthorEntity get(long id) { + return repository.findById(id) + .orElseThrow(() -> new NotFoundException(AuthorEntity.class, id)); } + @Transactional public AuthorEntity create(AuthorEntity entity) { - return repository.create(entity); + if (entity == null) { + throw new IllegalArgumentException("Creating AuthorEntity is null"); + } + checkNameUniqueness(entity.getName()); + return repository.save(entity); } - public AuthorEntity update(Long id, AuthorEntity entity) { + @Transactional + public AuthorEntity update(long id, AuthorEntity entity) { + if (entity == null) { + throw new IllegalArgumentException("Updating AuthorEntity is null"); + } final AuthorEntity existsEntity = get(id); + checkNameUniqueness(entity.getName()); existsEntity.setName(entity.getName()); - return repository.update(existsEntity); + return repository.save(existsEntity); } - public AuthorEntity delete(Long id) { + @Transactional + public AuthorEntity delete(long id) { final AuthorEntity existsEntity = get(id); - return repository.delete(existsEntity); + repository.delete(existsEntity); + return existsEntity; } } diff --git a/SpringApp/library/src/main/java/com/ip/library/authors_books/model/AuthorsBooksEntity.java b/SpringApp/library/src/main/java/com/ip/library/authors_books/model/AuthorsBooksEntity.java new file mode 100644 index 0000000..b5fbd3e --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/authors_books/model/AuthorsBooksEntity.java @@ -0,0 +1,81 @@ +package com.ip.library.authors_books.model; + +import java.util.Objects; + +import com.ip.library.authors.model.AuthorEntity; +import com.ip.library.books.model.BookEntity; + +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 = "authors_books") +public class AuthorsBooksEntity { + @EmbeddedId + private AuthorsBooksId id = new AuthorsBooksId(); + @ManyToOne + @MapsId("authorId") + @JoinColumn(name = "author_id") + private AuthorEntity author; + @ManyToOne + @MapsId("bookId") + @JoinColumn(name = "book_id") + private BookEntity book; + + public AuthorsBooksEntity() {} + + public AuthorsBooksEntity(AuthorEntity author, BookEntity book) { + this.author = author; + this.book = book; + } + + public void setId(AuthorsBooksId id) { + this.id = id; + } + + public AuthorsBooksId getId() { + return id; + } + + public void setAuthor(AuthorEntity author) { + this.author = author; + if (!author.getAuthorsBooks().contains(this)) + author.getAuthorsBooks().add(this); + } + + public AuthorEntity getAuthor() { + return author; + } + + public void setBook(BookEntity book) { + this.book = book; + if (!book.getAuthorsBooks().contains(this)) + book.getAuthorsBooks().add(this); + } + + public BookEntity getBook() { + return book; + } + + @Override + public int hashCode() { + return Objects.hash(id, author.getId(), book.getId()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + final AuthorsBooksEntity other = (AuthorsBooksEntity) obj; + return + Objects.equals(other.getId(), id) && + Objects.equals(other.getAuthor().getId(), author.getId()) && + Objects.equals(other.getBook().getId(), book.getId()); + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/authors_books/model/AuthorsBooksId.java b/SpringApp/library/src/main/java/com/ip/library/authors_books/model/AuthorsBooksId.java new file mode 100644 index 0000000..4329897 --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/authors_books/model/AuthorsBooksId.java @@ -0,0 +1,55 @@ +package com.ip.library.authors_books.model; + +import java.util.Objects; +import java.util.Optional; + +import jakarta.persistence.Embeddable; + +@Embeddable +public class AuthorsBooksId { + private Long authorId; + private Long bookId; + + public AuthorsBooksId() {} + + public AuthorsBooksId(Long authorId, Long bookId) { + this.authorId = authorId; + this.bookId = bookId; + } + + public Long getAuthorId() { + return authorId; + } + + public void setAuthorId(Long authorId) { + this.authorId = authorId; + } + + public Long getBookId() { + return bookId; + } + + public void setBookId(Long bookId) { + this.bookId = bookId; + } + + @Override + public int hashCode() { + return Objects.hash( + Optional.ofNullable(authorId).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; + AuthorsBooksId other = (AuthorsBooksId) obj; + return + Objects.equals(other.authorId, authorId) && + Objects.equals(other.bookId, bookId); + } +} 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 7ecb7a9..537a191 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 @@ -17,61 +17,72 @@ import com.ip.library.books.model.BookEntity; import com.ip.library.books.service.BookService; import com.ip.library.core.configuration.Constants; import com.ip.library.types.service.TypeService; -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; + public BookController(BookService bookService, TypeService typeService, ModelMapper modelMapper) { + this.bookService = bookService; this.typeService = typeService; - this.authorService = authorService; this.modelMapper = modelMapper; } - - private BookDto toDto(BookEntity entity) { - return modelMapper.map(entity, BookDto.class); - } + + private BookDto toBookDto (BookEntity entity) { + BookDto bookDto = modelMapper.map(entity, BookDto.class); + bookDto.setAuthorId(entity.getAuthorsBooks().stream().map(x -> x.getAuthor().getId()).toList()); + return bookDto; + } 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())); return entity; } @GetMapping public List getAll( - @RequestParam(name = "typeId", defaultValue = "0") Long typeId, - @RequestParam(name = "authorId", defaultValue = "0") Long authorId) { - return itemService.getAll(typeId, authorId).stream().map(this::toDto).toList(); + @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 bookService.getAll(typeId, authorId, page, size).stream().map(this::toBookDto).toList(); } @GetMapping("/{id}") public BookDto get(@PathVariable(name = "id") Long id) { - return toDto(itemService.get(id)); + return toBookDto(bookService.get(id)); } @PostMapping public BookDto create(@RequestBody @Valid BookDto dto) { - return toDto(itemService.create(toEntity(dto))); + return toBookDto(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 toBookDto(bookService.update(id, toEntity(dto))); } @DeleteMapping("/{id}") public BookDto delete(@PathVariable(name = "id") Long id) { - return toDto(itemService.delete(id)); + return toBookDto(bookService.delete(id)); } + + @GetMapping("/{bookId}/users/number") + public int getBookSubscribersNumber(@PathVariable(name = "bookId") Long bookId) { + return bookService.getBookSubscribersNumber(bookId); + } + + @GetMapping("/{bookId}/author/{authorId}") + public boolean addAuthor( + @PathVariable(name = "bookId") Long bookId, + @PathVariable(name = "authorId") Long authorId) { + return bookService.addAuthor(authorId, 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 c8e54e9..41346ef 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; @@ -7,6 +9,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; public class BookDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotBlank private String name; @@ -14,10 +17,8 @@ public class BookDto { @Min(1) private Long typeId; @NotNull - @Min(1) - private Long authorId; + private List authorsId; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) public Long getId() { return id; } @@ -26,6 +27,14 @@ public class BookDto { this.id = id; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public Long getTypeId() { return typeId; } @@ -34,11 +43,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 e208a76..296afd5 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,25 +1,46 @@ package com.ip.library.books.model; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import com.ip.library.core.model.BaseEntity; import com.ip.library.types.model.TypeEntity; -import com.ip.library.authors.model.AuthorEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.Table; + +import com.ip.library.authors.model.AuthorEntity; +import com.ip.library.authors_books.model.AuthorsBooksEntity; + +@Entity +@Table(name = "books") public class BookEntity extends BaseEntity { + @Column(nullable = false, unique = true, length = 50) private String name; + @ManyToOne + @JoinColumn(name = "type_id", nullable = false) + @OrderBy("id ASC") private TypeEntity type; - private AuthorEntity author; + @OneToMany(mappedBy = "book", cascade = CascadeType.ALL, + orphanRemoval = true, fetch = FetchType.EAGER) + @OrderBy("id ASC") + private Set authorsBooks = new HashSet<>(); public BookEntity() { super(); } - public BookEntity(Long id, String name, TypeEntity type, AuthorEntity author) { - super(id); + public BookEntity(String name, TypeEntity type) { this.name = name; this.type = type; - this.author = author; } public String getName() { @@ -38,17 +59,25 @@ public class BookEntity extends BaseEntity { this.type = type; } - public AuthorEntity getAuthor() { - return author; + public Set getAuthorsBooks() { + return authorsBooks; } - public void setAuthor(AuthorEntity author) { - this.author = author; + public void setAuthors(Set authorsBooks) { + this.authorsBooks = authorsBooks; + } + + public boolean addAuthor(AuthorEntity author) { + AuthorsBooksEntity entity = new AuthorsBooksEntity(author, this); + boolean result = authorsBooks.add(entity); + if (!author.getAuthorsBooks().contains(entity)) + author.getAuthorsBooks().add(entity); + return result; } @Override public int hashCode() { - return Objects.hash(id, type, author); + return Objects.hash(id, name, type); } @Override @@ -58,9 +87,9 @@ 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); + return + Objects.equals(other.getId(), id) + && Objects.equals(other.getName(), name) + && Objects.equals(other.getType(), type); } } 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 55e82c7..2a49435 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 @@ -1,10 +1,58 @@ package com.ip.library.books.repository; -import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + +import org.springframework.data.domain.Page; +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; import com.ip.library.books.model.BookEntity; -import com.ip.library.core.repository.MapRepository; -@Repository -public class BookRepository extends MapRepository { +public interface BookRepository extends + CrudRepository, + PagingAndSortingRepository { + Optional findByNameIgnoreCase(String name); + + List findByTypeId(long typeId); + + Page findByTypeId(long typeId, Pageable pageable); + + @Query( + "select ab.book " + + "from AuthorsBooksEntity ab " + + "where ab.author.id = ?1 " + + "order by ab.book.id") + List findByAuthorId(Long authorId); + + @Query( + "select ab.book " + + "from AuthorsBooksEntity ab " + + "where ab.author.id = ?1 " + + "order by ab.book.id") + Page findByAuthorId(Long authorId, Pageable pageable); + + @Query( + "select ab.book " + + "from AuthorsBooksEntity ab " + + "where ab.author.id = ?1 and " + + "ab.book.type.id = ?2 " + + "order by ab.book.id") + List findByAuthorIdAndTypeId(Long authorId, Long typeId); + + @Query( + "select ab.book " + + "from AuthorsBooksEntity ab " + + "where ab.author.id = ?1 and " + + "ab.book.type.id = ?2 " + + "order by ab.book.id") + Page findByAuthorIdAndTypeId(Long authorId, Long typeId, 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 6828d25..e893c19 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 @@ -1,11 +1,15 @@ package com.ip.library.books.service; import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.stream.StreamSupport; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +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.repository.BookRepository; import com.ip.library.core.error.NotFoundException; @@ -13,49 +17,98 @@ import com.ip.library.core.error.NotFoundException; @Service public class BookService { private final BookRepository repository; + private final AuthorService authorService; - public BookService(BookRepository repository) { + public BookService(BookRepository repository, AuthorService authorService) { this.repository = repository; + this.authorService = authorService; } - public List getAll(Long typeId, Long authorId) { - List result = repository.getAll(); - if (!Objects.equals(typeId, 0L)){ - result = result.stream() - .filter(item -> item.getType().getId().equals(typeId)) - .toList(); + private void checkNameUniqueness(String name){ + if (repository.findByNameIgnoreCase(name).isPresent()) { + throw new IllegalArgumentException( + String.format("Book with name %s already exists", name) + ); } - if (!Objects.equals(authorId, 0L)){ - result = result.stream() - .filter(item -> item.getAuthor().getId().equals(authorId)) - .toList(); - } - return result; } - public List getAll() { - return repository.getAll(); + @Transactional(readOnly = true) + public List getAll(){ + return StreamSupport.stream(repository.findAll().spliterator(), false).toList(); } - public BookEntity get(Long id) { - return Optional.ofNullable(repository.get(id)) - .orElseThrow(() -> new NotFoundException(id)); + @Transactional(readOnly = true) + public Page getAll(long typeId, long authorId, int page, int size) { + PageRequest pageRequest = PageRequest.of(page, size); + if (typeId <= 0L && authorId <= 0L) + return repository.findAll(pageRequest); + if (authorId <= 0L) + return repository.findByTypeId(typeId, pageRequest); + if (typeId <= 0L) + return repository.findByAuthorId(authorId, pageRequest); + return repository.findByAuthorIdAndTypeId(authorId, typeId, pageRequest); } + @Transactional(readOnly = true) + public List getAll(long typeId, long authorId) { + if (typeId <= 0L && authorId <= 0L) + return StreamSupport.stream(repository.findAll().spliterator(), + false).toList(); + if (authorId <= 0L) + return repository.findByTypeId(typeId); + if (typeId <= 0L) + return repository.findByAuthorId(authorId); + return repository.findByAuthorIdAndTypeId(authorId, typeId); + } + + @Transactional(readOnly = true) + public BookEntity get(long id) { + return repository.findById(id) + .orElseThrow(() -> new NotFoundException(BookEntity.class, id)); + } + + @Transactional public BookEntity create(BookEntity entity) { - return repository.create(entity); + if (entity == null) { + throw new IllegalArgumentException("Creating BookEntity is null"); + } + checkNameUniqueness(entity.getName()); + return repository.save(entity); } - public BookEntity update(Long id, BookEntity entity) { + @Transactional + public BookEntity update(long id, BookEntity entity) { + if (entity == null) { + throw new IllegalArgumentException("Updating BookEntity is null"); + } final BookEntity existsEntity = get(id); + checkNameUniqueness(entity.getName()); existsEntity.setName(entity.getName()); existsEntity.setType(entity.getType()); - existsEntity.setAuthor(entity.getAuthor()); - return repository.update(existsEntity); + return repository.save(existsEntity); } - public BookEntity delete(Long id) { + @Transactional + public BookEntity delete(long id) { final BookEntity existsEntity = get(id); - return repository.delete(existsEntity); + repository.delete(existsEntity); + return existsEntity; + } + + @Transactional(readOnly = true) + public int getBookSubscribersNumber(long bookId) { + return repository.getBookSubscribersNumber(bookId); + } + + @Transactional + public boolean addAuthor(long authorId, long bookId) { + final AuthorEntity existsAuthor = authorService.get(authorId); + final BookEntity book = get(bookId); + return existsAuthor.addBook(book); + } + + @Transactional + public List findByAuthorId(long authorId) { + return repository.findByAuthorId(authorId); } } diff --git a/SpringApp/library/src/main/java/com/ip/library/core/api/PageDto.java b/SpringApp/library/src/main/java/com/ip/library/core/api/PageDto.java new file mode 100644 index 0000000..1de2fe4 --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/core/api/PageDto.java @@ -0,0 +1,97 @@ +package com.ip.library.core.api; + +import java.util.ArrayList; +import java.util.List; + +public class PageDto { + private List items = new ArrayList<>(); + private int itemsCount; + private int currentPage; + private int currentSize; + private int totalPages; + private long totalItems; + private boolean isFirst; + private boolean isLast; + private boolean hasNext; + private boolean hasPrevious; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public int getItemsCount() { + return itemsCount; + } + + public void setItemsCount(int itemsCount) { + this.itemsCount = itemsCount; + } + + public int getCurrentPage() { + return currentPage; + } + + public void setCurrentPage(int currentPage) { + this.currentPage = currentPage; + } + + public int getCurrentSize() { + return currentSize; + } + + public void setCurrentSize(int currentSize) { + this.currentSize = currentSize; + } + + public int getTotalPages() { + return totalPages; + } + + public void setTotalPages(int totalPages) { + this.totalPages = totalPages; + } + + public long getTotalItems() { + return totalItems; + } + + public void setTotalItems(long totalItems) { + this.totalItems = totalItems; + } + + public boolean isFirst() { + return isFirst; + } + + public void setFirst(boolean isFirst) { + this.isFirst = isFirst; + } + + public boolean isLast() { + return isLast; + } + + public void setLast(boolean isLast) { + this.isLast = isLast; + } + + public boolean isHasNext() { + return hasNext; + } + + public void setHasNext(boolean hasNext) { + this.hasNext = hasNext; + } + + public boolean isHasPrevious() { + return hasPrevious; + } + + public void setHasPrevious(boolean hasPrevious) { + this.hasPrevious = hasPrevious; + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/api/PageDtoMapper.java b/SpringApp/library/src/main/java/com/ip/library/core/api/PageDtoMapper.java new file mode 100644 index 0000000..71ce537 --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/core/api/PageDtoMapper.java @@ -0,0 +1,24 @@ +package com.ip.library.core.api; + +import java.util.function.Function; + +import org.springframework.data.domain.Page; + +public class PageDtoMapper { + private PageDtoMapper() {} + + public static PageDto toDto(Page page, Function mapper) { + final PageDto dto = new PageDto<>(); + dto.setItems(page.getContent().stream().map(mapper::apply).toList()); + dto.setItemsCount(page.getNumberOfElements()); + dto.setCurrentPage(page.getNumber()); + dto.setCurrentSize(page.getSize()); + dto.setTotalPages(page.getTotalPages()); + dto.setTotalItems(page.getTotalElements()); + dto.setFirst(page.isFirst()); + dto.setLast(page.isLast()); + dto.setHasNext(page.hasNext()); + dto.setHasPrevious(page.hasPrevious()); + return dto; + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java b/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java index 4e40f87..f5eb422 100644 --- a/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java +++ b/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java @@ -1,8 +1,11 @@ package com.ip.library.core.configuration; public class Constants { + public static final String SEQUENCE_NAME = "hibernate_sequence"; + public static final String API_URL = "/api/1.0"; - private Constants() { - } + public static final String DEFAULT_PAGE_SIZE = "5"; + + private Constants() {} } diff --git a/SpringApp/library/src/main/java/com/ip/library/core/error/NotFoundException.java b/SpringApp/library/src/main/java/com/ip/library/core/error/NotFoundException.java index ddabef7..028fc12 100644 --- a/SpringApp/library/src/main/java/com/ip/library/core/error/NotFoundException.java +++ b/SpringApp/library/src/main/java/com/ip/library/core/error/NotFoundException.java @@ -1,7 +1,7 @@ package com.ip.library.core.error; public class NotFoundException extends RuntimeException { - public NotFoundException(Long id) { - super(String.format("Entity with id [%s] is not found or not exists", id)); + public NotFoundException(Class clazz, Long id) { + super(String.format("%s with id [%s] is not found or not exists", clazz.getSimpleName(), id)); } } diff --git a/SpringApp/library/src/main/java/com/ip/library/core/model/BaseEntity.java b/SpringApp/library/src/main/java/com/ip/library/core/model/BaseEntity.java index 9e53df8..b942708 100644 --- a/SpringApp/library/src/main/java/com/ip/library/core/model/BaseEntity.java +++ b/SpringApp/library/src/main/java/com/ip/library/core/model/BaseEntity.java @@ -1,15 +1,23 @@ package com.ip.library.core.model; +import com.ip.library.core.configuration.Constants; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.SequenceGenerator; + +@MappedSuperclass public abstract class BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = Constants.SEQUENCE_NAME) + @SequenceGenerator(name = Constants.SEQUENCE_NAME, sequenceName = Constants.SEQUENCE_NAME, allocationSize = 1) protected Long id; protected BaseEntity() { } - protected BaseEntity(Long id) { - this.id = id; - } - public Long getId() { return id; } diff --git a/SpringApp/library/src/main/java/com/ip/library/core/repository/CommonRepository.java b/SpringApp/library/src/main/java/com/ip/library/core/repository/CommonRepository.java deleted file mode 100644 index e72aa9a..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/repository/CommonRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ip.library.core.repository; - -import java.util.List; - -public interface CommonRepository { - List getAll(); - - E get(T id); - - E create(E entity); - - E update(E entity); - - E delete(E entity); - - void deleteAll(); -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/repository/MapRepository.java b/SpringApp/library/src/main/java/com/ip/library/core/repository/MapRepository.java deleted file mode 100644 index e19f119..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/repository/MapRepository.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.ip.library.core.repository; - -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import com.ip.library.core.model.BaseEntity; - -public abstract class MapRepository implements CommonRepository { - private final Map entities = new TreeMap<>(); - private Long lastId = 0L; - - protected MapRepository() { - } - - @Override - public List getAll() { - return entities.values().stream().toList(); - } - - @Override - public E get(Long id) { - return entities.get(id); - } - - @Override - public E create(E entity) { - lastId++; - entity.setId(lastId); - entities.put(lastId, entity); - return entity; - } - - @Override - public E update(E entity) { - if (get(entity.getId()) == null) { - return null; - } - entities.put(entity.getId(), entity); - return entity; - } - - @Override - public E delete(E entity) { - if (get(entity.getId()) == null) { - return null; - } - entities.remove(entity.getId()); - return entity; - } - - @Override - public void deleteAll() { - lastId = 0L; - entities.clear(); - } -} 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/types/api/TypeDto.java b/SpringApp/library/src/main/java/com/ip/library/types/api/TypeDto.java index e7626b9..49583bf 100644 --- a/SpringApp/library/src/main/java/com/ip/library/types/api/TypeDto.java +++ b/SpringApp/library/src/main/java/com/ip/library/types/api/TypeDto.java @@ -3,13 +3,15 @@ package com.ip.library.types.api; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; public class TypeDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotBlank + @Size(min = 5, max = 20) private String name; - - @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public Long getId() { return id; } 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 4e945a4..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 @@ -4,15 +4,21 @@ import java.util.Objects; import com.ip.library.core.model.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +@Entity +@Table(name = "types") public class TypeEntity extends BaseEntity { + @Column(nullable = false, unique = true, length = 20) private String name; public TypeEntity() { super(); } - public TypeEntity(Long id, String name) { - super(id); + public TypeEntity(String name) { this.name = name; } @@ -36,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/types/repository/TypeRepository.java b/SpringApp/library/src/main/java/com/ip/library/types/repository/TypeRepository.java index 1648303..1bf1394 100644 --- a/SpringApp/library/src/main/java/com/ip/library/types/repository/TypeRepository.java +++ b/SpringApp/library/src/main/java/com/ip/library/types/repository/TypeRepository.java @@ -1,10 +1,11 @@ package com.ip.library.types.repository; -import org.springframework.stereotype.Repository; +import java.util.Optional; + +import org.springframework.data.repository.CrudRepository; -import com.ip.library.core.repository.MapRepository; import com.ip.library.types.model.TypeEntity; -@Repository -public class TypeRepository extends MapRepository { +public interface TypeRepository extends CrudRepository { + Optional findByNameIgnoreCase(String name); } diff --git a/SpringApp/library/src/main/java/com/ip/library/types/service/TypeService.java b/SpringApp/library/src/main/java/com/ip/library/types/service/TypeService.java index 33f353f..82eff00 100644 --- a/SpringApp/library/src/main/java/com/ip/library/types/service/TypeService.java +++ b/SpringApp/library/src/main/java/com/ip/library/types/service/TypeService.java @@ -1,9 +1,10 @@ package com.ip.library.types.service; import java.util.List; -import java.util.Optional; +import java.util.stream.StreamSupport; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.ip.library.core.error.NotFoundException; import com.ip.library.types.model.TypeEntity; @@ -17,27 +18,49 @@ public class TypeService { this.repository = repository; } + private void checkNameUniqueness(String name){ + if (repository.findByNameIgnoreCase(name).isPresent()) { + throw new IllegalArgumentException( + String.format("Type with name %s already exists", name) + ); + } + } + + @Transactional(readOnly = true) public List getAll() { - return repository.getAll(); + return StreamSupport.stream(repository.findAll().spliterator(), false).toList(); } - public TypeEntity get(Long id) { - return Optional.ofNullable(repository.get(id)) - .orElseThrow(() -> new NotFoundException(id)); + @Transactional(readOnly = true) + public TypeEntity get(long id) { + return repository.findById(id) + .orElseThrow(() -> new NotFoundException(TypeEntity.class, id)); } + @Transactional public TypeEntity create(TypeEntity entity) { - return repository.create(entity); + if (entity == null) { + throw new IllegalArgumentException("Creating TypeEntity is null"); + } + checkNameUniqueness(entity.getName()); + return repository.save(entity); } - public TypeEntity update(Long id, TypeEntity entity) { + @Transactional + public TypeEntity update(long id, TypeEntity entity) { + if (entity == null) { + throw new IllegalArgumentException("Updating TypeEntity is null"); + } final TypeEntity existsEntity = get(id); + checkNameUniqueness(entity.getName()); existsEntity.setName(entity.getName()); - return repository.update(existsEntity); + return repository.save(existsEntity); } - public TypeEntity delete(Long id) { + @Transactional + public TypeEntity delete(long id) { final TypeEntity existsEntity = get(id); - return repository.delete(existsEntity); + repository.delete(existsEntity); + return existsEntity; } } 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 6da222c..c28a36a 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 @@ -10,8 +10,13 @@ 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.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; import com.ip.library.users.model.UserEntity; import com.ip.library.users.service.UserService; @@ -28,52 +33,72 @@ public class UserController { this.userService = userService; this.modelMapper = modelMapper; } + + private BookDto toBookDto (BookEntity entity) { + BookDto bookDto = modelMapper.map(entity, BookDto.class); + bookDto.setAuthorId(entity.getAuthorsBooks().stream().map(x -> x.getAuthor().getId()).toList()); + return bookDto; + } - private UserDto toDto(UserEntity entity) { + private UserDto toUserDto(UserEntity entity) { return modelMapper.map(entity, UserDto.class); } - private UserEntity toEntity(UserDto dto) { + private UserEntity toUserEntity(UserDto dto) { return modelMapper.map(dto, UserEntity.class); } @GetMapping - public List getAll() { - return userService.getAll().stream().map(this::toDto).toList(); + 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::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}") + @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)); } - @GetMapping("{id}/books/{bookId}") - public boolean addBook(@PathVariable(name = "id") Long id, @PathVariable(name = "bookId") Long bookId) { - return userService.addBook(id, bookId); + @DeleteMapping("/{userId}/books/{bookId}") + public boolean removeFavorite( + @PathVariable(name = "userId") Long userId, + @PathVariable(name = "bookId") Long bookId) { + return true; } - @DeleteMapping("{id}/books/{bookId}") - public boolean removeBook(@PathVariable(name = "id") Long id, @PathVariable(name = "bookId") Long bookId) { - return userService.removeBook(id, 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, + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { + return userService.getUserFavorities(userId, page, size).stream().map(this::toBookDto).toList(); + } } diff --git a/SpringApp/library/src/main/java/com/ip/library/users/api/UserDto.java b/SpringApp/library/src/main/java/com/ip/library/users/api/UserDto.java index c5070f7..52efe25 100644 --- a/SpringApp/library/src/main/java/com/ip/library/users/api/UserDto.java +++ b/SpringApp/library/src/main/java/com/ip/library/users/api/UserDto.java @@ -1,24 +1,25 @@ package com.ip.library.users.api; -import java.util.List; - import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; public class UserDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotBlank - private String name; + @Size(min = 5, max = 20) + private String login; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) @NotBlank + @Size(min = 5, max = 20) private String password; - @NotBlank - private String role; - @NotNull - private List books; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @NotBlank + @Size(min = 4, max = 20) + private String role; + public Long getId() { return id; } @@ -27,15 +28,14 @@ public class UserDto { this.id = id; } - public String getName() { - return name; + public String getLogin() { + return login; } - public void setName(String name) { - this.name = name; + public void setLogin(String name) { + this.login = name; } - @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getPassword() { return password; } @@ -44,7 +44,6 @@ public class UserDto { this.password = password; } - @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getRole() { return role; } @@ -52,13 +51,4 @@ public class UserDto { public void setRole(String role) { this.role = role; } - - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - public List getBooks() { - return books; - } - - public void setBooks(List books) { - this.books = books; - } } 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 7e9b559..756654a 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 @@ -1,35 +1,52 @@ package com.ip.library.users.model; -import java.util.List; +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 com.ip.library.favorites.model.FavoriteEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.Table; + +@Entity +@Table(name = "users") public class UserEntity extends BaseEntity { - private String name; + @Column(nullable = false, unique = true, length = 20) + private String login; + @Column(nullable = false, unique = false, length = 20) private String password; - private String role; - private List books; + @Column(nullable = false, unique = false, length = 20) + private String role = "user"; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @OrderBy("id ASC") + private Set favorites = new HashSet<>(); public UserEntity() { super(); } - public UserEntity(Long id, String name, String password, String role, List books) { - super(id); - this.name = name; + public UserEntity(String login, String password) { + this.login = login; this.password = password; - this.role = role; - this.books = books; } - public String getName() { - return name; + public UserEntity(String login) { + this.login = login; } - public void setName(String name) { - this.name = name; + public String getLogin() { + return login; + } + + public void setLogin(String name) { + this.login = name; } public String getPassword() { @@ -48,17 +65,25 @@ public class UserEntity extends BaseEntity { this.role = role; } - public List getBooks() { - return books; + public Set getFavorites() { + return favorites; } - public void setBooks(List books) { - this.books = books; + public void setFavorites(Set favorites) { + this.favorites = favorites; + } + + public boolean addBook(BookEntity book) { + return favorites.add(new FavoriteEntity(this, book)); + } + + public boolean removeBook(BookEntity book) { + return favorites.remove(new FavoriteEntity(this, book)); } @Override public int hashCode() { - return Objects.hash(id, name, password, role); + return Objects.hash(id, login, password, role); } @Override @@ -68,10 +93,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.getName(), name) - && 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 65e1f8f..8c78de8 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,10 +1,33 @@ package com.ip.library.users.repository; -import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; -import com.ip.library.core.repository.MapRepository; +import org.springframework.data.domain.Page; +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; + +import com.ip.library.books.model.BookEntity; import com.ip.library.users.model.UserEntity; -@Repository -public class UserRepository extends MapRepository { +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); + + @Query( + "select f.book " + + "from FavoriteEntity f " + + "where f.user.id = ?1 " + + "order by f.book.id") + public Page getUserFavorities(Long userId, Pageable pageable); } 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 1629e26..1809381 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 @@ -1,10 +1,12 @@ package com.ip.library.users.service; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import java.util.stream.StreamSupport; +import org.springframework.data.domain.Page; +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; @@ -15,66 +17,93 @@ import com.ip.library.users.repository.UserRepository; @Service public class UserService { private final UserRepository repository; - private final BookService bookService; + private final BookService bookService; public UserService(UserRepository repository, BookService bookService) { this.repository = repository; this.bookService = bookService; } + + private void checkLoginUniqueness(String name){ + if (repository.findByLoginIgnoreCase(name).isPresent()) { + throw new IllegalArgumentException( + String.format("Type with name %s already exists", name) + ); + } + } + @Transactional(readOnly = true) public List getAll() { - return repository.getAll(); + return StreamSupport.stream(repository.findAll().spliterator(), false).toList(); } - public UserEntity get(Long id) { - return Optional.ofNullable(repository.get(id)) - .orElseThrow(() -> new NotFoundException(id)); + @Transactional(readOnly = true) + public Page getAll(int page, int size) { + return repository.findAll(PageRequest.of(page, size)); } + @Transactional(readOnly = true) + public UserEntity get(long id) { + return repository.findById(id) + .orElseThrow(() -> new NotFoundException(UserEntity.class, id)); + } + + @Transactional public UserEntity create(UserEntity entity) { - entity.setRole("user"); - entity.setBooks(new ArrayList<>()); - return repository.create(entity); + checkLoginUniqueness(entity.getLogin()); + return repository.save(entity); } - public UserEntity update(Long id, UserEntity entity) { + @Transactional + public UserEntity update(long id, UserEntity entity) { final UserEntity existsEntity = get(id); - existsEntity.setName(entity.getName()); - return repository.update(existsEntity); + checkLoginUniqueness(entity.getLogin()); + existsEntity.setLogin(entity.getLogin()); + return repository.save(existsEntity); } - public UserEntity delete(Long id) { + @Transactional + public UserEntity delete(long id) { final UserEntity existsEntity = get(id); - return repository.delete(existsEntity); + repository.delete(existsEntity); + return existsEntity; } - public UserEntity giveAdminRole(Long id) { + @Transactional + public UserEntity giveAdminRole(long id) { final UserEntity existsEntity = get(id); existsEntity.setRole("admin"); - return repository.update(existsEntity); + return repository.save(existsEntity); } - public UserEntity giveUserRole(Long id) { + @Transactional + public UserEntity giveUserRole(long id) { final UserEntity existsEntity = get(id); existsEntity.setRole("user"); - return repository.update(existsEntity); + return repository.save(existsEntity); } - public UserEntity changePassword(Long id, String newPassword) { + @Transactional + public UserEntity changePassword(long id, String newPassword) { final UserEntity existsEntity = get(id); existsEntity.setPassword(newPassword); - return repository.update(existsEntity); + return repository.save(existsEntity); } - public boolean addBook(Long id, Long bookId) { - final UserEntity existsEntity = get(id); - final BookEntity book = bookService.get(bookId); - return existsEntity.getBooks().add(book); + @Transactional + public boolean addFavorite(long userId, long bookId) { + final UserEntity existsUser = get(userId); + final BookEntity book = bookService.get(bookId); + return existsUser.addBook(book); } - public boolean removeBook(Long id, Long bookId) { - final UserEntity existsEntity = get(id); - final BookEntity book = bookService.get(bookId); - return existsEntity.getBooks().remove(book); + @Transactional(readOnly = true) + public List getUserFavorities (long userId) { + return repository.getUserFavorities(userId); + } + + @Transactional(readOnly = true) + public Page getUserFavorities (long userId, int page, int size) { + return repository.getUserFavorities(userId, PageRequest.of(page, size)); } } diff --git a/SpringApp/library/src/main/resources/application-dev.properties b/SpringApp/library/src/main/resources/application-dev.properties new file mode 100644 index 0000000..4ed3960 --- /dev/null +++ b/SpringApp/library/src/main/resources/application-dev.properties @@ -0,0 +1,8 @@ +# Dev H2 JPA Settings +spring.datasource.url=jdbc:h2:file:./data +spring.datasource.username=sa +spring.datasource.password=password +spring.datasource.driver-class-name=org.h2.Driver + +# H2 console +spring.h2.console.enabled=true \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/application-prod.properties b/SpringApp/library/src/main/resources/application-prod.properties new file mode 100644 index 0000000..aaf9d7d --- /dev/null +++ b/SpringApp/library/src/main/resources/application-prod.properties @@ -0,0 +1,9 @@ +# Logger settings +# Available levels are: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF +logging.level.com.example.demo=INFO + +# Prod PostgreSQL JPA Settings +spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/demo-app +spring.datasource.username=postgres +spring.datasource.password=postgres +spring.datasource.driver-class-name=org.postgresql.Driver diff --git a/SpringApp/library/src/main/resources/application.properties b/SpringApp/library/src/main/resources/application.properties index 8b13789..3e18e21 100644 --- a/SpringApp/library/src/main/resources/application.properties +++ b/SpringApp/library/src/main/resources/application.properties @@ -1 +1,21 @@ +# Server +spring.main.banner-mode=off +server.port=8080 +# Logger settings +# Available levels are: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF +logging.level.com.example.demo=DEBUG + +# Set default profile +spring.profiles.default=dev + +# Common JPA Settings +spring.jpa.hibernate.ddl-auto=create +spring.jpa.open-in-view=false +# spring.jpa.show-sql=true +# spring.jpa.properties.hibernate.format_sql=true + +# Liquibase Settings +spring.liquibase.enabled=true +spring.liquibase.drop-first=false +spring.liquibase.change-log=classpath:db/changelog.xml \ No newline at end of file diff --git a/SpringApp/library/src/test/java/com/ip/library/AuthorsTests.java b/SpringApp/library/src/test/java/com/ip/library/AuthorsTests.java index 8015c9c..6b24ede 100644 --- a/SpringApp/library/src/test/java/com/ip/library/AuthorsTests.java +++ b/SpringApp/library/src/test/java/com/ip/library/AuthorsTests.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; import com.ip.library.core.error.NotFoundException; import com.ip.library.authors.model.AuthorEntity; @@ -25,9 +26,9 @@ class AuthorsTests { @BeforeEach void createData() { removeData(); - authorService.create(new AuthorEntity(null, "author1")); - authorService.create(new AuthorEntity(null, "author2")); - entity = authorService.create(new AuthorEntity(null, "author3")); + authorService.create(new AuthorEntity("author1")); + authorService.create(new AuthorEntity("author2")); + entity = authorService.create(new AuthorEntity("author3")); } @Test @@ -44,14 +45,11 @@ class AuthorsTests { @Test void updateTest() { - final Long testId; - if (entity.getId() != 1L) - testId = 1L; - else testId = 2L; + final Long oldId = entity.getId(); final String testName = entity.getName() + "TEST"; - entity = authorService.update(entity.getId(), new AuthorEntity(testId, testName)); + entity = authorService.update(oldId, new AuthorEntity(testName)); Assertions.assertEquals(3, authorService.getAll().size()); - Assertions.assertNotEquals(testId, entity.getId()); + Assertions.assertEquals(oldId, entity.getId()); Assertions.assertEquals(testName, entity.getName()); } @@ -59,8 +57,32 @@ class AuthorsTests { void deleteTest() { authorService.delete(entity.getId()); Assertions.assertEquals(2, authorService.getAll().size()); - final AuthorEntity newEntity = authorService.create(new AuthorEntity(null, entity.getName())); + final AuthorEntity newEntity = authorService.create(new AuthorEntity(entity.getName())); Assertions.assertEquals(3, authorService.getAll().size()); Assertions.assertNotEquals(entity.getId(), newEntity.getId()); } + + @Test + void nullNameTest() { + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> authorService.create(new AuthorEntity(null)) + ); + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> authorService.update(entity.getId(), new AuthorEntity(null)) + ); + } + + @Test + void uniqueNameTest() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> authorService.create(new AuthorEntity(entity.getName())) + ); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> authorService.update(entity.getId(), new AuthorEntity(entity.getName())) + ); + } } 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 3041484..98e79a2 100644 --- a/SpringApp/library/src/test/java/com/ip/library/BooksTests.java +++ b/SpringApp/library/src/test/java/com/ip/library/BooksTests.java @@ -1,11 +1,14 @@ package com.ip.library; +import java.util.List; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; import com.ip.library.core.error.NotFoundException; import com.ip.library.types.model.TypeEntity; @@ -23,9 +26,11 @@ class BooksTests { private TypeService typeService; @Autowired private AuthorService authorService; - private BookEntity book; - private TypeEntity type; - private AuthorEntity author; + private BookEntity book1; + private BookEntity book2; + private BookEntity book3; + private TypeEntity type1; + private AuthorEntity author1; @AfterEach void removeData() { @@ -37,52 +42,103 @@ class BooksTests { @BeforeEach void createData() { removeData(); - type = typeService.create(new TypeEntity(null, "type1")); - var type2 = typeService.create(new TypeEntity(null, "type2")); - author = authorService.create(new AuthorEntity(null, "author1")); - var author2 = authorService.create(new AuthorEntity(null, "author2")); - bookService.create(new BookEntity(null, "book1", type, author2)); - bookService.create(new BookEntity(null, "book2", type2, author)); - book = bookService.create(new BookEntity(null, "book3", type, author)); + type1 = typeService.create(new TypeEntity("type1")); + var type2 = typeService.create(new TypeEntity("type2")); + author1 = authorService.create(new AuthorEntity("author1")); + var author2 = authorService.create(new AuthorEntity("author2")); + book1 = bookService.create(new BookEntity("book1", type1)); + book2 = bookService.create(new BookEntity("book2", type1)); + book3 = bookService.create(new BookEntity("book3", type2)); + bookService.addAuthor(author1.getId(), book1.getId()); + bookService.addAuthor(author1.getId(), book3.getId()); + bookService.addAuthor(author2.getId(), book1.getId()); + bookService.addAuthor(author2.getId(), book2.getId()); } @Test void createTest() { Assertions.assertEquals(3, bookService.getAll().size()); - Assertions.assertEquals("book3", book.getName()); - Assertions.assertEquals(type, book.getType()); - Assertions.assertEquals(author, book.getAuthor()); + Assertions.assertEquals("book1", book1.getName()); + Assertions.assertEquals(type1, book1.getType()); + Assertions.assertEquals(0, bookService.getBookSubscribersNumber(book1.getId())); } @Test void getTest() { - Assertions.assertEquals(book, bookService.get(book.getId())); + Assertions.assertEquals(book1, bookService.get(book1.getId())); Assertions.assertThrows(NotFoundException.class, () -> bookService.get(0L)); } @Test void updateTest() { - final Long testId; - if (book.getId() != 1L) - testId = 1L; - else testId = 2L; - final String testName = book.getName() + "TEST"; - final TypeEntity testType = typeService.create(new TypeEntity(null, book.getType().getName() + "TEST")); - final AuthorEntity testAuthor = authorService.create(new AuthorEntity(null, book.getAuthor().getName() + "TEST")); - book = bookService.update(book.getId(), new BookEntity(testId, testName, testType, testAuthor)); + final String testName = book1.getName() + "TEST"; + final TypeEntity testType = typeService.create( + new TypeEntity(book1.getType().getName() + "TEST")); + book1 = bookService.update(book1.getId(), new BookEntity(testName, testType)); Assertions.assertEquals(3, bookService.getAll().size()); - Assertions.assertNotEquals(testId, book.getId()); - Assertions.assertEquals(testName, book.getName()); - Assertions.assertEquals(testType, book.getType()); - Assertions.assertEquals(testAuthor, book.getAuthor()); + Assertions.assertEquals(testName, book1.getName()); + Assertions.assertEquals(testType, book1.getType()); } @Test void deleteTest() { - bookService.delete(book.getId()); + bookService.delete(book1.getId()); Assertions.assertEquals(2, bookService.getAll().size()); - final BookEntity newEntity = bookService.create(new BookEntity(null, book.getName(), book.getType(), book.getAuthor())); + final BookEntity newEntity = bookService.create( + new BookEntity(book1.getName(), book1.getType())); Assertions.assertEquals(3, bookService.getAll().size()); - Assertions.assertNotEquals(book.getId(), newEntity.getId()); + Assertions.assertNotEquals(book1.getId(), newEntity.getId()); + } + + @Test + void nullNameTest() { + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> bookService.create(new BookEntity(null, book1.getType())) + ); + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> bookService.update(book1.getId(), new BookEntity(null, book1.getType())) + ); + } + + @Test + void nullTypeTest() { + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> bookService.create(new BookEntity(book1.getName() + "TEST", null)) + ); + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> bookService.update(book1.getId(), new BookEntity(book1.getName() + "TEST", null)) + ); + } + + @Test + void uniqueNameTest() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> bookService.create(new BookEntity(book1.getName(), book1.getType())) + ); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> bookService.update(book1.getId(), new BookEntity(book1.getName(), book1.getType())) + ); + } + + @Test + void getByFilterTest() { + //filter by type + List list = bookService.getAll(type1.getId(), -1L); + Assertions.assertEquals(2, list.size()); + Assertions.assertTrue(list.contains(book1) && list.contains(book2)); + //filter by author + list = bookService.getAll(-1L, author1.getId()); + Assertions.assertEquals(2, list.size()); + Assertions.assertTrue(list.contains(book1) && list.contains(book3)); + //filter by type and author + list = bookService.getAll(type1.getId(), author1.getId()); + Assertions.assertEquals(1, list.size()); + Assertions.assertTrue(list.contains(book1)); } } diff --git a/SpringApp/library/src/test/java/com/ip/library/FavoritesTests.java b/SpringApp/library/src/test/java/com/ip/library/FavoritesTests.java new file mode 100644 index 0000000..f3427cf --- /dev/null +++ b/SpringApp/library/src/test/java/com/ip/library/FavoritesTests.java @@ -0,0 +1,66 @@ +package com.ip.library; + +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.ip.library.books.model.BookEntity; +import com.ip.library.books.service.BookService; +import com.ip.library.types.model.TypeEntity; +import com.ip.library.types.service.TypeService; +import com.ip.library.users.model.UserEntity; +import com.ip.library.users.service.UserService; + +@SpringBootTest +class FavoritesTests { + @Autowired + private BookService bookService; + @Autowired + private UserService userService; + @Autowired + private TypeService typeService; + private UserEntity user1; + private UserEntity user2; + private BookEntity book1; + private BookEntity book2; + + @AfterEach + void removeData() { + userService.getAll().forEach(item -> userService.delete(item.getId())); + bookService.getAll().forEach(item -> bookService.delete(item.getId())); + typeService.getAll().forEach(item -> typeService.delete(item.getId())); + } + + @BeforeEach + void createData() { + removeData(); + TypeEntity type = typeService.create(new TypeEntity("type1")); + book1 = bookService.create(new BookEntity("book1", type)); + book2 = bookService.create(new BookEntity("book2", type)); + user1 = userService.create(new UserEntity("user3", "aqw2sed45")); + user2 = userService.create(new UserEntity("user1", "123")); + userService.create(new UserEntity("user2", "456")); + userService.addFavorite(user1.getId(), book2.getId()); + userService.addFavorite(user2.getId(), book1.getId()); + userService.addFavorite(user2.getId(), book2.getId()); + } + + @Test + void getBookSubscribersNumberTest() { + Assertions.assertEquals(1, bookService.getBookSubscribersNumber(book1.getId())); + Assertions.assertEquals(2, bookService.getBookSubscribersNumber(book2.getId())); + } + + @Test + void getUserFavoritiesTest() { + List list = userService.getUserFavorities(user2.getId()); + Assertions.assertEquals(2, list.size()); + Assertions.assertTrue(list.contains(book1)); + Assertions.assertTrue(list.contains(book2)); + } +} diff --git a/SpringApp/library/src/test/java/com/ip/library/FavoritiesTests.java b/SpringApp/library/src/test/java/com/ip/library/FavoritiesTests.java deleted file mode 100644 index b061e45..0000000 --- a/SpringApp/library/src/test/java/com/ip/library/FavoritiesTests.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.ip.library; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -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.types.model.TypeEntity; -import com.ip.library.users.model.UserEntity; -import com.ip.library.types.service.TypeService; -import com.ip.library.users.service.UserService; - -@SpringBootTest -class FavoritiesTests { - @Autowired - private BookService bookService; - @Autowired - private TypeService typeService; - @Autowired - private AuthorService authorService; - @Autowired - private UserService userService; - private UserEntity user; - private BookEntity book1; - private BookEntity book2; - - @AfterEach - void removeData() { - authorService.getAll().forEach(item -> authorService.delete(item.getId())); - bookService.getAll().forEach(item -> bookService.delete(item.getId())); - typeService.getAll().forEach(item -> typeService.delete(item.getId())); - userService.getAll().forEach(item -> userService.delete(item.getId())); - } - - @BeforeEach - void createData() { - removeData(); - var type = typeService.create(new TypeEntity(null, "type1")); - var author = authorService.create(new AuthorEntity(null, "author1")); - user = userService.create(new UserEntity(null, "user", "password", null, null)); - book1 = bookService.create(new BookEntity(null, "book1", type, author)); - book2 = bookService.create(new BookEntity(null, "book2", type, author)); - } - - @Test - void addAndRemoveBooksTest() { - Assertions.assertEquals(0, user.getBooks().size()); - userService.addBook(user.getId(), book1.getId()); - Assertions.assertEquals(1, user.getBooks().size()); - userService.addBook(user.getId(), book2.getId()); - Assertions.assertEquals(2, user.getBooks().size()); - Assertions.assertEquals(2, user.getBooks().size()); - userService.removeBook(user.getId(), book1.getId()); - Assertions.assertEquals(1, user.getBooks().size()); - userService.removeBook(user.getId(), book2.getId()); - Assertions.assertEquals(0, user.getBooks().size()); - } -} diff --git a/SpringApp/library/src/test/java/com/ip/library/TypesTests.java b/SpringApp/library/src/test/java/com/ip/library/TypesTests.java index da03ba3..c0dd067 100644 --- a/SpringApp/library/src/test/java/com/ip/library/TypesTests.java +++ b/SpringApp/library/src/test/java/com/ip/library/TypesTests.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; import com.ip.library.core.error.NotFoundException; import com.ip.library.types.model.TypeEntity; @@ -25,9 +26,9 @@ class TypesTests { @BeforeEach void createData() { removeData(); - typeService.create(new TypeEntity(null, "type1")); - typeService.create(new TypeEntity(null, "type2")); - entity = typeService.create(new TypeEntity(null, "type3")); + typeService.create(new TypeEntity("type1")); + typeService.create(new TypeEntity("type2")); + entity = typeService.create(new TypeEntity("type3")); } @Test @@ -39,19 +40,17 @@ class TypesTests { @Test void getTest() { Assertions.assertEquals(entity, typeService.get(entity.getId())); - Assertions.assertThrows(NotFoundException.class, () -> typeService.get(0L)); + Assertions.assertThrows( + NotFoundException.class, () -> typeService.get(0L)); } @Test void updateTest() { - final Long testId; - if (entity.getId() != 1L) - testId = 1L; - else testId = 2L; + final Long oldId = entity.getId(); final String testName = entity.getName() + "TEST"; - entity = typeService.update(entity.getId(), new TypeEntity(testId, testName)); + entity = typeService.update(oldId, new TypeEntity(testName)); Assertions.assertEquals(3, typeService.getAll().size()); - Assertions.assertNotEquals(testId, entity.getId()); + Assertions.assertEquals(oldId, entity.getId()); Assertions.assertEquals(testName, entity.getName()); } @@ -59,8 +58,33 @@ class TypesTests { void deleteTest() { typeService.delete(entity.getId()); Assertions.assertEquals(2, typeService.getAll().size()); - final TypeEntity newEntity = typeService.create(new TypeEntity(null, entity.getName())); + final TypeEntity newEntity = typeService.create( + new TypeEntity(entity.getName())); Assertions.assertEquals(3, typeService.getAll().size()); Assertions.assertNotEquals(entity.getId(), newEntity.getId()); } + + @Test + void nullNameTest() { + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> typeService.create(new TypeEntity(null)) + ); + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> typeService.update(entity.getId(), new TypeEntity(null)) + ); + } + + @Test + void uniqueNameTest() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> typeService.create(new TypeEntity(entity.getName())) + ); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> typeService.update(entity.getId(), new TypeEntity(entity.getName())) + ); + } } diff --git a/SpringApp/library/src/test/java/com/ip/library/UserEntity.java b/SpringApp/library/src/test/java/com/ip/library/UserEntity.java deleted file mode 100644 index 7e3437b..0000000 --- a/SpringApp/library/src/test/java/com/ip/library/UserEntity.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.ip.library; - -public class UserEntity { - -} 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 1c3afc2..77d86a1 100644 --- a/SpringApp/library/src/test/java/com/ip/library/UsersTests.java +++ b/SpringApp/library/src/test/java/com/ip/library/UsersTests.java @@ -1,66 +1,43 @@ package com.ip.library; -import java.util.ArrayList; -import java.util.List; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; -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.core.error.NotFoundException; -import com.ip.library.types.model.TypeEntity; -import com.ip.library.types.service.TypeService; import com.ip.library.users.model.UserEntity; import com.ip.library.users.service.UserService; @SpringBootTest class UsersTests { - @Autowired - private BookService bookService; - @Autowired - private TypeService typeService; - @Autowired - private AuthorService authorService; @Autowired private UserService userService; private UserEntity user; - private BookEntity book; @AfterEach void removeData() { - authorService.getAll().forEach(item -> authorService.delete(item.getId())); - bookService.getAll().forEach(item -> bookService.delete(item.getId())); - typeService.getAll().forEach(item -> typeService.delete(item.getId())); userService.getAll().forEach(item -> userService.delete(item.getId())); } @BeforeEach void createData() { removeData(); - var type1 = typeService.create(new TypeEntity(null, "type1")); - var author1 = authorService.create(new AuthorEntity(null, "author1")); - book = bookService.create(new BookEntity(null, "book1", type1, author1)); - List books = new ArrayList(); - books.add(book); - userService.create(new UserEntity(null, "user1", "123", "user", null)); - userService.create(new UserEntity(null, "user2", "456", "user", null)); - user = userService.create(new UserEntity(null, "user3", "aqw2sed45", "admin", books)); + userService.create(new UserEntity("user1", "123")); + userService.create(new UserEntity("user2", "456")); + user = userService.create(new UserEntity("user3", "aqw2sed45")); } @Test void createTest() { Assertions.assertEquals(3, userService.getAll().size()); - Assertions.assertEquals("user3", user.getName()); + 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 @@ -72,31 +49,23 @@ class UsersTests { @Test void updateTest() { - final Long testId; - if (user.getId() != 1L) - testId = 1L; - else testId = 2L; - final String testName = user.getName() + "TEST"; + final Long oldId = user.getId(); + final String testName = user.getLogin() + "TEST"; final String testPassword = user.getPassword() + "TEST"; - final String testRole = "admin"; - List testBooks = new ArrayList(); - testBooks.add(book); - user = userService.update(user.getId(), new UserEntity(testId, testName, - testPassword, testRole, testBooks)); + user = userService.update(oldId, new UserEntity(testName, testPassword)); Assertions.assertEquals(3, userService.getAll().size()); - Assertions.assertNotEquals(testId, user.getId()); - Assertions.assertEquals(testName, user.getName()); + Assertions.assertEquals(oldId, user.getId()); + Assertions.assertEquals(testName, user.getLogin()); Assertions.assertNotEquals(testPassword, user.getPassword()); - Assertions.assertNotEquals(testRole, user.getRole()); - Assertions.assertEquals(0, user.getBooks().size()); + Assertions.assertEquals(0, userService.getUserFavorities(user.getId()).size()); } @Test void deleteTest() { userService.delete(user.getId()); Assertions.assertEquals(2, userService.getAll().size()); - final UserEntity newEntity = userService.create(new UserEntity(null, - user.getName(), user.getPassword(), null, null)); + final UserEntity newEntity = userService.create( + new UserEntity(user.getLogin(), user.getPassword())); Assertions.assertEquals(3, userService.getAll().size()); Assertions.assertNotEquals(user.getId(), newEntity.getId()); } @@ -104,16 +73,52 @@ class UsersTests { @Test void changePasswordTest() { String newPassword = user.getPassword() + "TEST"; - userService.changePassword(user.getId(), newPassword); + user = userService.changePassword(user.getId(), newPassword); Assertions.assertEquals(newPassword, user.getPassword()); } @Test void changeRoleTest() { Assertions.assertEquals("user", user.getRole()); - userService.giveAdminRole(user.getId()); + user = userService.giveAdminRole(user.getId()); Assertions.assertEquals("admin", user.getRole()); - userService.giveUserRole(user.getId()); + user = userService.giveUserRole(user.getId()); Assertions.assertEquals("user", user.getRole()); } + + @Test + void nullLoginTest() { + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> userService.create(new UserEntity(null, user.getPassword())) + ); + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> userService.update(user.getId(), new UserEntity(null)) + ); + } + + @Test + void nullPasswordTest() { + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> userService.create(new UserEntity(user.getLogin() + "TEST", null)) + ); + Assertions.assertThrows( + DataIntegrityViolationException.class, + () -> userService.changePassword(user.getId(), null) + ); + } + + @Test + void uniqueLoginTest() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> userService.create(new UserEntity(user.getLogin(), user.getPassword())) + ); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> userService.update(user.getId(), new UserEntity(user.getLogin())) + ); + } }