mvc add searh and favorites table

This commit is contained in:
Zakharov_Rostislav 2024-06-06 21:35:41 +04:00
parent 989809469d
commit 92414d74c8
6 changed files with 248 additions and 15 deletions

View File

@ -0,0 +1,5 @@
package com.ip.library.controllers.favorites;
import org.springframework.data.repository.CrudRepository;
public interface FavoriteRepository extends CrudRepository<FavoriteEntity, UserBookId> {}

View File

@ -0,0 +1,108 @@
package com.ip.library.controllers.users;
import java.util.List;
import java.util.Map;
import org.modelmapper.ModelMapper;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ip.library.controllers.authors.AuthorEntity;
import com.ip.library.controllers.books.BookDto;
import com.ip.library.controllers.books.BookEntity;
import com.ip.library.controllers.books.BookService;
import com.ip.library.core.api.PageAttributesMapper;
import com.ip.library.core.configuration.Constants;
import com.ip.library.core.security.UserPrincipal;
@Controller
public class UserBookController {
private static final String BOOK_SEARCH_VIEW = "book-search";
private static final String USER_FAVORITES_VIEW = "user-favorites";
private static final String PAGE_ATTRIBUTE = "page";
private final UserService userService;
private final BookService bookService;
private final ModelMapper modelMapper;
public UserBookController(
UserService userService,
BookService bookService,
ModelMapper modelMapper) {
this.bookService = bookService;
this.userService = userService;
this.modelMapper = modelMapper;
}
private BookDto toBookDto (BookEntity entity) {
BookDto bookDto = modelMapper.map(entity, BookDto.class);
List<AuthorEntity> authors = entity.getAuthorsBooks().stream().map(x -> x.getAuthor()).toList();
bookDto.setAuthorsId(authors.stream().map(x -> x.getId()).toList());
bookDto.setTypeName(entity.getType().getName());
StringBuilder authorName = new StringBuilder();
for (AuthorEntity authorEntity : authors) {
authorName.append(", ").append(authorEntity.getName());
}
if (authorName.length() > 0) {
bookDto.setAuthorName(authorName.toString().substring(2));
} else {
bookDto.setAuthorName("Неизвестен");
}
return bookDto;
}
@GetMapping
public String getFavorites(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model,
@AuthenticationPrincipal UserPrincipal principal) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
userService.getUserFavorities(principal.getId(), page, Constants.DEFUALT_PAGE_SIZE),
this::toBookDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_FAVORITES_VIEW;
}
@PostMapping(Constants.API_URL + "/removeFavorite/{id}")
public String removeFavorite(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
RedirectAttributes redirectAttributes,
@AuthenticationPrincipal UserPrincipal principal) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.removeFavorite(principal.getId(), id);
return Constants.REDIRECT_VIEW + "/";
}
@GetMapping(Constants.API_URL + "/search")
public String getAll(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@RequestParam(name = "typeId", defaultValue = "-1") Long typeId,
@RequestParam(name = "authorId", defaultValue = "-1") Long authorId,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
bookService.getAll(typeId, authorId, page, Constants.DEFUALT_PAGE_SIZE),
this::toBookDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return BOOK_SEARCH_VIEW;
}
@PostMapping(Constants.API_URL + "/addFavorite/{id}")
public String addFavorite(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
RedirectAttributes redirectAttributes,
@AuthenticationPrincipal UserPrincipal principal) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.addFavorite(principal.getId(), id);
return Constants.REDIRECT_VIEW + Constants.API_URL + "/search";
}
}

View File

@ -16,27 +16,33 @@ import org.springframework.util.StringUtils;
import com.ip.library.controllers.books.BookEntity;
import com.ip.library.controllers.books.BookService;
import com.ip.library.controllers.favorites.FavoriteEntity;
import com.ip.library.controllers.favorites.FavoriteRepository;
import com.ip.library.controllers.favorites.UserBookId;
import com.ip.library.core.configuration.Constants;
import com.ip.library.core.error.NotFoundException;
import com.ip.library.core.security.UserPrincipal;
@Service
public class UserService implements UserDetailsService{
private final UserRepository repository;
private final UserRepository userRepository;
private final FavoriteRepository favoriteRepository;
private final BookService bookService;
private final PasswordEncoder passwordEncoder;
public UserService(
UserRepository repository,
FavoriteRepository favoriteRepository,
BookService bookService,
PasswordEncoder passwordEncoder) {
this.repository = repository;
this.userRepository = repository;
this.favoriteRepository = favoriteRepository;
this.bookService = bookService;
this.passwordEncoder = passwordEncoder;
}
private void checkLoginUniqueness(String name){
if (repository.findByLoginIgnoreCase(name).isPresent()) {
if (userRepository.findByLoginIgnoreCase(name).isPresent()) {
throw new IllegalArgumentException(
String.format("Type with name %s already exists", name)
);
@ -45,23 +51,23 @@ public class UserService implements UserDetailsService{
@Transactional(readOnly = true)
public List<UserEntity> getAll() {
return StreamSupport.stream(repository.findAll().spliterator(), false).toList();
return StreamSupport.stream(userRepository.findAll().spliterator(), false).toList();
}
@Transactional(readOnly = true)
public Page<UserEntity> getAll(int page, int size) {
return repository.findAll(PageRequest.of(page, size));
return userRepository.findAll(PageRequest.of(page, size));
}
@Transactional(readOnly = true)
public UserEntity get(long id) {
return repository.findById(id)
return userRepository.findById(id)
.orElseThrow(() -> new NotFoundException(UserEntity.class, id));
}
@Transactional(readOnly = true)
public UserEntity getByLogin(String login) {
return repository.findByLoginIgnoreCase(login)
return userRepository.findByLoginIgnoreCase(login)
.orElseThrow(() -> new IllegalArgumentException("Invalid login"));
}
@ -77,7 +83,7 @@ public class UserService implements UserDetailsService{
passwordEncoder.encode(
StringUtils.hasText(password.strip()) ? password : Constants.DEFAULT_PASSWORD));
entity.setRole(Optional.ofNullable(entity.getRole()).orElse(UserRole.USER));
return repository.save(entity);
return userRepository.save(entity);
}
@Transactional
@ -85,13 +91,13 @@ public class UserService implements UserDetailsService{
final UserEntity existsEntity = get(id);
checkLoginUniqueness(entity.getLogin());
existsEntity.setLogin(entity.getLogin());
return repository.save(existsEntity);
return userRepository.save(existsEntity);
}
@Transactional
public UserEntity delete(long id) {
final UserEntity existsEntity = get(id);
repository.delete(existsEntity);
userRepository.delete(existsEntity);
return existsEntity;
}
@ -99,21 +105,21 @@ public class UserService implements UserDetailsService{
public UserEntity giveAdminRole(long id) {
final UserEntity existsEntity = get(id);
existsEntity.setRole(UserRole.ADMIN);
return repository.save(existsEntity);
return userRepository.save(existsEntity);
}
@Transactional
public UserEntity giveUserRole(long id) {
final UserEntity existsEntity = get(id);
existsEntity.setRole(UserRole.USER);
return repository.save(existsEntity);
return userRepository.save(existsEntity);
}
@Transactional
public UserEntity changePassword(long id, String newPassword) {
final UserEntity existsEntity = get(id);
existsEntity.setPassword(newPassword);
return repository.save(existsEntity);
return userRepository.save(existsEntity);
}
@Transactional
@ -123,14 +129,22 @@ public class UserService implements UserDetailsService{
return existsUser.addBook(book);
}
@Transactional
public FavoriteEntity removeFavorite(long userId, long bookId) {
final FavoriteEntity existsEntity = favoriteRepository.findById(new UserBookId(userId, bookId))
.orElseThrow(() -> new IllegalArgumentException("Invalid id"));
favoriteRepository.delete(existsEntity);
return existsEntity;
}
@Transactional(readOnly = true)
public List<BookEntity> getUserFavorities (long userId) {
return repository.getUserFavorities(userId);
return userRepository.getUserFavorities(userId);
}
@Transactional(readOnly = true)
public Page<BookEntity> getUserFavorities (long userId, int page, int size) {
return repository.getUserFavorities(userId, PageRequest.of(page, size));
return userRepository.getUserFavorities(userId, PageRequest.of(page, size));
}
@Override

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Поиск</title>
</head>
<body>
<main layout:fragment="content">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2>Поиск</h2>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Название</th>
<th scope="col" class="w-auto">Жанр</th>
<th scope="col" class="w-auto">Автор</th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${items}">
<th scope="row" th:text="${book.id}"></th>
<td th:text="${book.name}"></td>
<td th:text="${book.typeName}"></td>
<td th:text="${book.authorName}"></td>
<td>
<form th:action="@{/api/1.0/addFavorite/{id}(id=${book.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">В избранное</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url=${'api/1.0/search'},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</th:block>
</main>
</body>
</html>

View File

@ -28,6 +28,14 @@
</button>
<div class="collapse navbar-collapse" id="main-navbar">
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
<a class="nav-link" href="/api/1.0/search"
th:classappend="${activeLink.startsWith('/api/1.0/search') ? 'active' : ''}">
Поиск
</a>
<a class="nav-link" href="/"
th:classappend="${activeLink.startsWith('/api/1.0/search') ? 'active' : ''}">
Избранное
</a>
<th:block sec:authorize="hasRole('ADMIN')">
<a class="nav-link" href="/api/1.0/user"
th:classappend="${activeLink.startsWith('/api/1.0/user') ? 'active' : ''}">

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Избранное</title>
</head>
<body>
<main layout:fragment="content">
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<h2>Избранное</h2>
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Название</th>
<th scope="col" class="w-auto">Жанр</th>
<th scope="col" class="w-auto">Автор</th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${items}">
<th scope="row" th:text="${book.id}"></th>
<td th:text="${book.name}"></td>
<td th:text="${book.typeName}"></td>
<td th:text="${book.authorName}"></td>
<td>
<form th:action="@{/api/1.0/removeFavorite/{id}(id=${book.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url=${''},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</th:block>
</main>
</body>
</html>