mvc add searh and favorites table
This commit is contained in:
parent
989809469d
commit
92414d74c8
@ -0,0 +1,5 @@
|
|||||||
|
package com.ip.library.controllers.favorites;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
public interface FavoriteRepository extends CrudRepository<FavoriteEntity, UserBookId> {}
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -16,27 +16,33 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
import com.ip.library.controllers.books.BookEntity;
|
import com.ip.library.controllers.books.BookEntity;
|
||||||
import com.ip.library.controllers.books.BookService;
|
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.configuration.Constants;
|
||||||
import com.ip.library.core.error.NotFoundException;
|
import com.ip.library.core.error.NotFoundException;
|
||||||
import com.ip.library.core.security.UserPrincipal;
|
import com.ip.library.core.security.UserPrincipal;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class UserService implements UserDetailsService{
|
public class UserService implements UserDetailsService{
|
||||||
private final UserRepository repository;
|
private final UserRepository userRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
private final BookService bookService;
|
private final BookService bookService;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
UserRepository repository,
|
UserRepository repository,
|
||||||
|
FavoriteRepository favoriteRepository,
|
||||||
BookService bookService,
|
BookService bookService,
|
||||||
PasswordEncoder passwordEncoder) {
|
PasswordEncoder passwordEncoder) {
|
||||||
this.repository = repository;
|
this.userRepository = repository;
|
||||||
|
this.favoriteRepository = favoriteRepository;
|
||||||
this.bookService = bookService;
|
this.bookService = bookService;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkLoginUniqueness(String name){
|
private void checkLoginUniqueness(String name){
|
||||||
if (repository.findByLoginIgnoreCase(name).isPresent()) {
|
if (userRepository.findByLoginIgnoreCase(name).isPresent()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
String.format("Type with name %s already exists", name)
|
String.format("Type with name %s already exists", name)
|
||||||
);
|
);
|
||||||
@ -45,23 +51,23 @@ public class UserService implements UserDetailsService{
|
|||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<UserEntity> getAll() {
|
public List<UserEntity> getAll() {
|
||||||
return StreamSupport.stream(repository.findAll().spliterator(), false).toList();
|
return StreamSupport.stream(userRepository.findAll().spliterator(), false).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Page<UserEntity> getAll(int page, int size) {
|
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)
|
@Transactional(readOnly = true)
|
||||||
public UserEntity get(long id) {
|
public UserEntity get(long id) {
|
||||||
return repository.findById(id)
|
return userRepository.findById(id)
|
||||||
.orElseThrow(() -> new NotFoundException(UserEntity.class, id));
|
.orElseThrow(() -> new NotFoundException(UserEntity.class, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public UserEntity getByLogin(String login) {
|
public UserEntity getByLogin(String login) {
|
||||||
return repository.findByLoginIgnoreCase(login)
|
return userRepository.findByLoginIgnoreCase(login)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Invalid login"));
|
.orElseThrow(() -> new IllegalArgumentException("Invalid login"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +83,7 @@ public class UserService implements UserDetailsService{
|
|||||||
passwordEncoder.encode(
|
passwordEncoder.encode(
|
||||||
StringUtils.hasText(password.strip()) ? password : Constants.DEFAULT_PASSWORD));
|
StringUtils.hasText(password.strip()) ? password : Constants.DEFAULT_PASSWORD));
|
||||||
entity.setRole(Optional.ofNullable(entity.getRole()).orElse(UserRole.USER));
|
entity.setRole(Optional.ofNullable(entity.getRole()).orElse(UserRole.USER));
|
||||||
return repository.save(entity);
|
return userRepository.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -85,13 +91,13 @@ public class UserService implements UserDetailsService{
|
|||||||
final UserEntity existsEntity = get(id);
|
final UserEntity existsEntity = get(id);
|
||||||
checkLoginUniqueness(entity.getLogin());
|
checkLoginUniqueness(entity.getLogin());
|
||||||
existsEntity.setLogin(entity.getLogin());
|
existsEntity.setLogin(entity.getLogin());
|
||||||
return repository.save(existsEntity);
|
return userRepository.save(existsEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public UserEntity delete(long id) {
|
public UserEntity delete(long id) {
|
||||||
final UserEntity existsEntity = get(id);
|
final UserEntity existsEntity = get(id);
|
||||||
repository.delete(existsEntity);
|
userRepository.delete(existsEntity);
|
||||||
return existsEntity;
|
return existsEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,21 +105,21 @@ public class UserService implements UserDetailsService{
|
|||||||
public UserEntity giveAdminRole(long id) {
|
public UserEntity giveAdminRole(long id) {
|
||||||
final UserEntity existsEntity = get(id);
|
final UserEntity existsEntity = get(id);
|
||||||
existsEntity.setRole(UserRole.ADMIN);
|
existsEntity.setRole(UserRole.ADMIN);
|
||||||
return repository.save(existsEntity);
|
return userRepository.save(existsEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public UserEntity giveUserRole(long id) {
|
public UserEntity giveUserRole(long id) {
|
||||||
final UserEntity existsEntity = get(id);
|
final UserEntity existsEntity = get(id);
|
||||||
existsEntity.setRole(UserRole.USER);
|
existsEntity.setRole(UserRole.USER);
|
||||||
return repository.save(existsEntity);
|
return userRepository.save(existsEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public UserEntity changePassword(long id, String newPassword) {
|
public UserEntity changePassword(long id, String newPassword) {
|
||||||
final UserEntity existsEntity = get(id);
|
final UserEntity existsEntity = get(id);
|
||||||
existsEntity.setPassword(newPassword);
|
existsEntity.setPassword(newPassword);
|
||||||
return repository.save(existsEntity);
|
return userRepository.save(existsEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -123,14 +129,22 @@ public class UserService implements UserDetailsService{
|
|||||||
return existsUser.addBook(book);
|
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)
|
@Transactional(readOnly = true)
|
||||||
public List<BookEntity> getUserFavorities (long userId) {
|
public List<BookEntity> getUserFavorities (long userId) {
|
||||||
return repository.getUserFavorities(userId);
|
return userRepository.getUserFavorities(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Page<BookEntity> getUserFavorities (long userId, int page, int size) {
|
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
|
@Override
|
||||||
|
@ -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>
|
@ -28,6 +28,14 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="main-navbar">
|
<div class="collapse navbar-collapse" id="main-navbar">
|
||||||
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
|
<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')">
|
<th:block sec:authorize="hasRole('ADMIN')">
|
||||||
<a class="nav-link" href="/api/1.0/user"
|
<a class="nav-link" href="/api/1.0/user"
|
||||||
th:classappend="${activeLink.startsWith('/api/1.0/user') ? 'active' : ''}">
|
th:classappend="${activeLink.startsWith('/api/1.0/user') ? 'active' : ''}">
|
||||||
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user