diff --git a/src/main/java/ru/mcs/sopds/controller/HomeController.java b/src/main/java/ru/mcs/sopds/controller/HomeController.java deleted file mode 100644 index 20401db..0000000 --- a/src/main/java/ru/mcs/sopds/controller/HomeController.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.mcs.sopds.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class HomeController { - - @GetMapping("/") - public String home(Model model) { - model.addAttribute("message", "Добро пожаловать в SOPDS на Java!"); - model.addAttribute("version", "1.0.0"); - return "home"; - } -} \ No newline at end of file diff --git a/src/main/java/ru/mcs/sopds/controller/WebController.java b/src/main/java/ru/mcs/sopds/controller/WebController.java new file mode 100644 index 0000000..321a2ba --- /dev/null +++ b/src/main/java/ru/mcs/sopds/controller/WebController.java @@ -0,0 +1,191 @@ +package ru.mcs.sopds.controller; + +import ru.mcs.sopds.entity.*; +import ru.mcs.sopds.service.BookService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Controller +@RequiredArgsConstructor +public class WebController { + + private final BookService bookService; + + @GetMapping("/") + public String home(Model model) { + // Статистика + Map stats = new HashMap<>(); + stats.put("booksCount", bookService.getBooksCount()); + stats.put("authorsCount", bookService.getAuthorsCount()); + stats.put("seriesCount", bookService.getSeriesCount()); + stats.put("genresCount", bookService.getGenresCount()); + + model.addAttribute("stats", stats); + model.addAttribute("recentBooks", bookService.getRecentBooks(8)); + model.addAttribute("popularGenres", bookService.getPopularGenres(10)); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "index"; + } + + @GetMapping("/books") + public String books( + @RequestParam(defaultValue = "0") int page, + @RequestParam(required = false) Long genreId, + @RequestParam(required = false) String format, + @RequestParam(required = false) String lang, + Model model) { + + Pageable pageable = PageRequest.of(page, 20); + Page books; + + if (genreId != null) { + books = bookService.getBooksByGenre(genreId, pageable); + model.addAttribute("genre", bookService.getGenreById(genreId)); + } else { + books = bookService.getAllBooks(pageable); + } + + model.addAttribute("books", books); + model.addAttribute("allGenres", bookService.getAllGenres()); + model.addAttribute("formats", List.of("fb2", "epub", "pdf", "mobi", "djvu")); + model.addAttribute("genreId", genreId); + model.addAttribute("format", format); + model.addAttribute("lang", lang); + model.addAttribute("pageUrl", "/books"); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "books"; + } + + @GetMapping("/authors") + public String authors( + @RequestParam(defaultValue = "0") int page, + @RequestParam(required = false) String letter, + Model model) { + + Pageable pageable = PageRequest.of(page, 100); + Page authors; + + if (letter != null && !letter.isEmpty()) { + authors = bookService.getAuthorsByLetter(letter, pageable); + } else { + authors = bookService.getAllAuthors(pageable); + } + + model.addAttribute("authors", authors); + model.addAttribute("letter", letter); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "authors"; + } + + @GetMapping("/genres") + public String genres(Model model) { + model.addAttribute("genres", bookService.getAllGenres()); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + return "genres"; + } + + @GetMapping("/series") + public String series( + @RequestParam(defaultValue = "0") int page, + Model model) { + + Pageable pageable = PageRequest.of(page, 50); + Page series = bookService.getAllSeries(pageable); + + model.addAttribute("series", series); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "series"; + } + + @GetMapping("/book/{id}") + public String bookDetail(@PathVariable Long id, Model model) { + Book book = bookService.getBookById(id); + model.addAttribute("book", book); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + return "book_detail"; + } + + @GetMapping("/books/author/{id}") + public String booksByAuthor( + @PathVariable Long id, + @RequestParam(defaultValue = "0") int page, + Model model) { + + Pageable pageable = PageRequest.of(page, 20); + Author author = bookService.getAuthorById(id); + Page books = bookService.getBooksByAuthor(id, pageable); + + model.addAttribute("author", author); + model.addAttribute("books", books); + model.addAttribute("pageUrl", "/books/author/" + id); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "books"; + } + + @GetMapping("/books/series/{id}") + public String booksBySeries( + @PathVariable Long id, + @RequestParam(defaultValue = "0") int page, + Model model) { + + Pageable pageable = PageRequest.of(page, 20); + Series series = bookService.getSeriesById(id); + Page books = bookService.getBooksBySeries(id, pageable); + + model.addAttribute("series", series); + model.addAttribute("books", books); + model.addAttribute("pageUrl", "/books/series/" + id); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "books"; + } + + @GetMapping("/books/genre/{id}") + public String booksByGenre( + @PathVariable Long id, + @RequestParam(defaultValue = "0") int page, + Model model) { + + Pageable pageable = PageRequest.of(page, 20); + Genre genre = bookService.getGenreById(id); + Page books = bookService.getBooksByGenre(id, pageable); + + model.addAttribute("genre", genre); + model.addAttribute("books", books); + model.addAttribute("pageUrl", "/books/genre/" + id); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "books"; + } + + @GetMapping("/search") + public String search( + @RequestParam String q, + @RequestParam(defaultValue = "0") int page, + Model model) { + + Pageable pageable = PageRequest.of(page, 20); + Page books = bookService.searchBooks(q, pageable); + + model.addAttribute("books", books); + model.addAttribute("query", q); + model.addAttribute("pageUrl", "/search?q=" + q); + model.addAttribute("topGenres", bookService.getPopularGenres(5)); + + return "search"; + } +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/sopds/entity/Book.java b/src/main/java/ru/mcs/sopds/entity/Book.java index f341a1a..83f3f46 100644 --- a/src/main/java/ru/mcs/sopds/entity/Book.java +++ b/src/main/java/ru/mcs/sopds/entity/Book.java @@ -2,6 +2,8 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; + import java.time.LocalDateTime; import java.util.HashSet; import java.util.Set; @@ -9,9 +11,11 @@ @Data @Entity @Table(name = "books") +@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include private Long id; @Column(name = "title", nullable = false) diff --git a/src/main/java/ru/mcs/sopds/entity/Genre.java b/src/main/java/ru/mcs/sopds/entity/Genre.java index 534a736..9866a0b 100644 --- a/src/main/java/ru/mcs/sopds/entity/Genre.java +++ b/src/main/java/ru/mcs/sopds/entity/Genre.java @@ -2,15 +2,20 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + import java.util.HashSet; import java.util.Set; @Data @Entity @Table(name = "genres") +@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class Genre { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include private Long id; @Column(name = "genre", nullable = false) @@ -23,5 +28,17 @@ private String subsection; @ManyToMany(mappedBy = "genres") + @ToString.Exclude private Set books = new HashSet<>(); + + // Transient field for book count + @Transient + private Long bookCount; + + public Long getBookCount() { + if (bookCount != null) { + return bookCount; + } + return books != null ? (long) books.size() : 0L; + } } \ No newline at end of file diff --git a/src/main/java/ru/mcs/sopds/repository/AuthorRepository.java b/src/main/java/ru/mcs/sopds/repository/AuthorRepository.java new file mode 100644 index 0000000..c90f687 --- /dev/null +++ b/src/main/java/ru/mcs/sopds/repository/AuthorRepository.java @@ -0,0 +1,12 @@ +package ru.mcs.sopds.repository; + +import ru.mcs.sopds.entity.Author; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AuthorRepository extends JpaRepository { + Page findByFullNameStartingWith(String letter, Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/sopds/repository/BookRepository.java b/src/main/java/ru/mcs/sopds/repository/BookRepository.java new file mode 100644 index 0000000..4e537da --- /dev/null +++ b/src/main/java/ru/mcs/sopds/repository/BookRepository.java @@ -0,0 +1,25 @@ +package ru.mcs.sopds.repository; + +import ru.mcs.sopds.entity.Book; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface BookRepository extends JpaRepository { + + @Query("SELECT b FROM Book b ORDER BY b.registerDate DESC") + List findTopNByOrderByRegisterDateDesc(Pageable pageable); + + Page findByAuthorsId(Long authorId, Pageable pageable); + + Page findBySeriesId(Long seriesId, Pageable pageable); + + Page findByGenresId(Long genreId, Pageable pageable); + + Page findByTitleContainingIgnoreCase(String title, Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/sopds/repository/GenreRepository.java b/src/main/java/ru/mcs/sopds/repository/GenreRepository.java new file mode 100644 index 0000000..00ae267 --- /dev/null +++ b/src/main/java/ru/mcs/sopds/repository/GenreRepository.java @@ -0,0 +1,22 @@ +package ru.mcs.sopds.repository; + + +import ru.mcs.sopds.entity.Genre; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface GenreRepository extends JpaRepository { + + @Query(value = "SELECT g.*, COUNT(bg.book_id) as book_count FROM genres g " + + "LEFT JOIN book_genre bg ON g.id = bg.genre_id " + + "GROUP BY g.id ORDER BY book_count DESC LIMIT ?1", + nativeQuery = true) + List findPopularGenres(int limit); + + @Query("SELECT g FROM Genre g LEFT JOIN FETCH g.books WHERE g.id = ?1") + Genre findByIdWithBooks(Long id); +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/sopds/repository/SeriesRepository.java b/src/main/java/ru/mcs/sopds/repository/SeriesRepository.java new file mode 100644 index 0000000..e09d5f7 --- /dev/null +++ b/src/main/java/ru/mcs/sopds/repository/SeriesRepository.java @@ -0,0 +1,9 @@ +package ru.mcs.sopds.repository; + +import ru.mcs.sopds.entity.Series; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SeriesRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/ru/mcs/sopds/service/BookService.java b/src/main/java/ru/mcs/sopds/service/BookService.java new file mode 100644 index 0000000..326ec20 --- /dev/null +++ b/src/main/java/ru/mcs/sopds/service/BookService.java @@ -0,0 +1,101 @@ +package ru.mcs.sopds.service; + +import org.springframework.data.jpa.repository.Query; +import ru.mcs.sopds.entity.*; +import ru.mcs.sopds.repository.*; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class BookService { + + private final BookRepository bookRepository; + private final AuthorRepository authorRepository; + private final GenreRepository genreRepository; + private final SeriesRepository seriesRepository; + + public Long getBooksCount() { + return bookRepository.count(); + } + + public Long getAuthorsCount() { + return authorRepository.count(); + } + + public Long getSeriesCount() { + return seriesRepository.count(); + } + + public Long getGenresCount() { + return genreRepository.count(); + } + + public List getRecentBooks(int limit) { + return bookRepository.findTopNByOrderByRegisterDateDesc(PageRequest.of(0, limit)); + } + + public List getPopularGenres(int limit) { + List genres = genreRepository.findPopularGenres(limit); + // Для нативных запросов нам нужно установить bookCount вручную + // или использовать Projection/DTO + return genres; + } + + public Page getAllBooks(Pageable pageable) { + return bookRepository.findAll(pageable); + } + + public Page getAllAuthors(Pageable pageable) { + return authorRepository.findAll(pageable); + } + + public Page getAuthorsByLetter(String letter, Pageable pageable) { + return authorRepository.findByFullNameStartingWith(letter, pageable); + } + + public List getAllGenres() { + return genreRepository.findAll(); + } + + public Page getAllSeries(Pageable pageable) { + return seriesRepository.findAll(pageable); + } + + public Book getBookById(Long id) { + return bookRepository.findById(id).orElse(null); + } + + public Author getAuthorById(Long id) { + return authorRepository.findById(id).orElse(null); + } + + public Series getSeriesById(Long id) { + return seriesRepository.findById(id).orElse(null); + } + + public Genre getGenreById(Long id) { + return genreRepository.findById(id).orElse(null); + } + + public Page getBooksByAuthor(Long authorId, Pageable pageable) { + return bookRepository.findByAuthorsId(authorId, pageable); + } + + public Page getBooksBySeries(Long seriesId, Pageable pageable) { + return bookRepository.findBySeriesId(seriesId, pageable); + } + + public Page getBooksByGenre(Long genreId, Pageable pageable) { + return bookRepository.findByGenresId(genreId, pageable); + } + + public Page searchBooks(String query, Pageable pageable) { + return bookRepository.findByTitleContainingIgnoreCase(query, pageable); + } +} \ No newline at end of file diff --git a/src/main/resources/templates/authors.html b/src/main/resources/templates/authors.html new file mode 100644 index 0000000..c8159d4 --- /dev/null +++ b/src/main/resources/templates/authors.html @@ -0,0 +1,92 @@ + + + + Авторы - SOPDS + + + +
+
+

Авторы

+
+
+ +
+
+
+ + +
+ + A +
+ + + + + + + +
+ +

Авторы не найдены

+

Попробуйте выбрать другую букву

+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/books.html b/src/main/resources/templates/books.html new file mode 100644 index 0000000..3aa0170 --- /dev/null +++ b/src/main/resources/templates/books.html @@ -0,0 +1,174 @@ + + + + Книги - SOPDS + + +
+
+

+ Книги жанра: Жанр + Книги автора: Автор + Книги серии: Серия + +

+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + Сбросить +
+
+
+
+ + +
+
+
+
+
+
+ +
+ format +
+
+
+
+ +
+ +

+ Авторы: + + + , + +

+ +

+ Серия: + +

+ +

+ Жанры: + + + , + +

+ +

+ + Размер: 0 MB + +

+ +

+ + Язык: + + Русский + Английский + + + +

+
+
+
+ +
+
+
+ + + + +
+ +

Книги не найдены

+

Попробуйте изменить параметры поиска или фильтры

+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000..c9bbe63 --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,145 @@ + + + + Главная - SOPDS + + +
+
+

Главная страница

+
+ + +
+
+
+
+
+
+

0

+

Книг

+
+
+ +
+
+
+
+
+
+
+
+
+
+

0

+

Авторов

+
+
+ +
+
+
+
+
+
+
+
+
+
+

0

+

Серий

+
+
+ +
+
+
+
+
+
+
+
+
+
+

0

+

Жанров

+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+ Последние добавленные книги +
+
+
+
+
+
+
+
+ +
+
Название книги
+

+ + Автор + , + +

+

+ формат +

+ + Подробнее + +
+
+
+
+
+

Нет недавно добавленных книг

+
+
+
+
+
+ + +
+
+
+
+
+ Популярные жанры +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html new file mode 100644 index 0000000..57ce5ad --- /dev/null +++ b/src/main/resources/templates/layout.html @@ -0,0 +1,201 @@ + + + + + + SOPDS + + + + + + + + + + + + + +
+
+ + + + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file