работает регистрация, разграничение прав доступа, отображаются, удаляются, редактируются пользоатели)) (всё для админа)

This commit is contained in:
Елена Бакальская 2024-05-19 22:55:45 +04:00
parent b04eacffd1
commit 8ce978e9cb
20 changed files with 352 additions and 36 deletions

View File

@ -29,8 +29,37 @@ public class BackendApplication implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
final var admin = new UserEntity(null, "elena", "1234");
final var vasya = new UserEntity(null, "vasya", "1234");
final var u1 = new UserEntity(null, "1", "1234");
final var u2 = new UserEntity(null, "2", "1234");
final var u3 = new UserEntity(null, "3", "1234");
final var u4 = new UserEntity(null, "4", "1234");
final var u5 = new UserEntity(null, "5", "1234");
final var u6 = new UserEntity(null, "6", "1234");
final var u7 = new UserEntity(null, "7", "1234");
admin.setRole(UserRole.ADMIN);
vasya.setRole(UserRole.USER);
u1.setRole(UserRole.USER);
u2.setRole(UserRole.USER);
u3.setRole(UserRole.USER);
u4.setRole(UserRole.USER);
u5.setRole(UserRole.USER);
u6.setRole(UserRole.USER);
u7.setRole(UserRole.USER);
userService.create(admin);
userService.create(vasya);
userService.create(u1);
userService.create(u2);
userService.create(u3);
userService.create(u4);
userService.create(u5);
userService.create(u6);
userService.create(u7);
_logger.info("Admin user added");
}

View File

@ -34,7 +34,7 @@ public class CategorieService {
@Transactional(readOnly = true)
public CategorieEntity get(Integer id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException(id));
return repository.findById(id).orElseThrow(() -> new NotFoundException(CategorieEntity.class, id));
}
@Transactional

View File

@ -1,7 +1,7 @@
package com.example.backend.core.errors;
public class NotFoundException extends RuntimeException {
public NotFoundException(Integer id) {
super(String.format("Сущность с айдишником <[%s]> не найден, либо его не существует", id));
public <T> NotFoundException(Class<T> clazz, Integer id) {
super(String.format("%s with id [%s] is not found or not exists", clazz.getSimpleName(), id));
}
}

View File

@ -28,7 +28,7 @@ public class FavoriteService {
@Transactional(readOnly = true)
public FavoriteEntity get(Integer id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException(id));
return repository.findById(id).orElseThrow(() -> new NotFoundException(FavoriteEntity.class, id));
}
@Transactional

View File

@ -37,7 +37,7 @@ public class MovieService {
@Transactional(readOnly = true)
public MovieEntity get(Integer id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException(id));
return repository.findById(id).orElseThrow(() -> new NotFoundException(MovieEntity.class, id));
}
@Transactional

View File

@ -1,28 +1,33 @@
package com.example.backend.users.api;
import java.util.Map;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.backend.core.api.PageAttributesMapper;
import com.example.backend.core.configurations.Constants;
import com.example.backend.users.model.UserEntity;
import com.example.backend.users.service.UserService;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
@Controller
// @RequestMapping(Constants.API_URL + "/user")
@RequestMapping(UserController.URL)
public class UserController {
private static final String CATEGORIES_VIEW = "categories";
private static final String LOGIN_VIEW = "login";
public static final String URL = Constants.ADMIN_PREFIX + "/user";
private static final String USER_VIEW = "user";
private static final String USER_EDIT_VIEW = "user-edit";
private static final String PAGE_ATTRIBUTE = "page";
private static final String USER_ATTRIBUTE = "user";
private final UserService userService;
@ -41,21 +46,81 @@ public class UserController {
return modelMapper.map(entity, UserDTO.class);
}
@GetMapping
public String getAll(
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
userService.getAll(page, Constants.DEFUALT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_VIEW;
}
@GetMapping("/edit/")
public String create(Model model) {
model.addAttribute(CATEGORIES_VIEW, new UserDTO());
return LOGIN_VIEW;
public String create(@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
model.addAttribute(USER_ATTRIBUTE, new UserDTO());
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
@PostMapping("/edit/")
public String create(
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDTO userDTO,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDTO user,
BindingResult bindingResult,
Model model) {
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
return LOGIN_VIEW + "/login";
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
userService.create(toEntity(userDTO));
return Constants.REDIRECT_VIEW + "/user";
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.create(toEntity(user));
return Constants.REDIRECT_VIEW + URL;
}
@GetMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Integer id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
if (id <= 0) {
throw new IllegalArgumentException();
}
model.addAttribute(USER_ATTRIBUTE, toDto(userService.get(id)));
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
@PostMapping("/edit/{id}")
public String update(
@PathVariable(name = "id") Integer id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDTO user,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
if (id <= 0) {
throw new IllegalArgumentException();
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.update(id, toEntity(user));
return Constants.REDIRECT_VIEW + URL;
}
@PostMapping("/delete/{id}")
public String delete(
@PathVariable(name = "id") Integer id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.delete(id);
return Constants.REDIRECT_VIEW + URL;
}
}

View File

@ -3,11 +3,15 @@ package com.example.backend.users.service;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@ -43,9 +47,14 @@ public class UserService implements UserDetailsService {
return StreamSupport.stream(repository.findAll().spliterator(), false).toList();
}
@Transactional(readOnly = true)
public Page<UserEntity> getAll(int page, int size) {
return repository.findAll(PageRequest.of(page, size, Sort.by("id")));
}
@Transactional(readOnly = true)
public UserEntity get(Integer id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException(id));
return repository.findById(id).orElseThrow(() -> new NotFoundException(UserEntity.class, id));
}
@Transactional(readOnly = true)

View File

@ -28,7 +28,7 @@ public class ViewedService {
@Transactional(readOnly = true)
public ViewedEntity get(Integer id) {
return repository.findById(id).orElseThrow(() -> new NotFoundException(id));
return repository.findById(id).orElseThrow(() -> new NotFoundException(ViewedEntity.class, id));
}
@Transactional
@ -50,10 +50,4 @@ public class ViewedService {
repository.delete(exisEntity);
return exisEntity;
}
// @Transactional
// public Integer countViewed(Integer movieId) {
// return repository.getCountViews(movieId);
// }
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<head>
<title>Вход</title>

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!-- <!DOCTYPE html>
<html lang="ru" data-bs-theme="dark" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
@ -32,8 +32,8 @@
<div class="collapse navbar-collapse" id="main-navbar">
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
<th:block sec:authorize="hasRole('ADMIN')">
<a class="nav-link" href="/admin/users"
th:classappend="${activeLink.startsWith('/admin/users')? 'active' : '' }">Пользователи</a>
<a class="nav-link" href="/admin/user"
th:classappend="${activeLink.startsWith('/admin/user')? 'active' : '' }">Пользователи</a>
<a class="nav-link" href="/admin/categories"
th:classappend="${activeLink.startsWith('/admin/categories')? 'active' : '' }">Категории</a>
<a class="nav-link" href="/admin/movies"
@ -73,4 +73,4 @@
</footer>
</body>
</html>
</html> -->

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<head>
<title>Ошибка</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<head>

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="ru" data-bs-theme="dark" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image" src="/images/logo_livecinema.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">LiveCinema</title>
<script type="text/javascript" src="/webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.3.3/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" />
<link rel="stylesheet" href="/css/style.css" />
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
</head>
<body class="d-flex flex-column flex-fill h-100">
<nav expand="md" data-bs-theme="dark" class="navbar my-navbar navbar-expand-md">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="/images/logo_livecinema.png" class="logo" style="height: 2vh;" />
LiveCinema
</a>
<th:block sec:authorize="isAuthenticated()" th:with="userName=${#authentication.name}">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar"
aria-controls="main-navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar">
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
<th:block sec:authorize="hasRole('ADMIN')">
<a class="nav-link" href="/admin/user"
th:classappend="${activeLink.startsWith('/admin/user')? 'active' : '' }">Пользователи</a>
<a class="nav-link" href="/admin/categories"
th:classappend="${activeLink.startsWith('/admin/categories')? 'active' : '' }">Категории</a>
<a class="nav-link" href="/admin/movies"
th:classappend="${activeLink.startsWith('/admin/movies') ? 'active' : '' }">Фильмы</a>
<a class="nav-link" href="/admin/favorites"
th:classappend="${activeLink.startsWith('/admin/favorites') ? 'active' : '' }">Избранные</a>
<a class="nav-link" href="/admin/viewed"
th:classappend="${activeLink.startsWith('/admin/viewed') ? 'active' : '' }">Просмотренные</a>
<a class="nav-link" href="/h2-console/" target="_blank">Консоль H2</a>
</th:block>
<th:block sec:authorize="hasRole('USER')">
<a class="nav-link" href="/categories"
th:classappend="${activeLink.startsWith('/categories') ? 'active' : '' }">Категории</a>
<a class="nav-link" href="/movies"
th:classappend="${activeLink.startsWith('/movies') ? 'active' : '' }">Фильмы</a>
<a class="nav-link" href="/favorites"
th:classappend="${activeLink.startsWith('/favorites') ? 'active' : '' }">Избранные</a>
<a class="nav-link" href="/viewed"
th:classappend="${activeLink.startsWith('/viewed') ? 'active' : '' }">Просмотренные</a>
</th:block>
</ul>
<ul class="navbar-nav" th:if="${not #strings.isEmpty(userName)}">
<form th:action="@{/logout}" method="post">
<button type="submit" class="navbar-brand nav-link" onclick="return confirm('Вы уверены?')">
Выход ([[${userName}]])
</button>
</form>
</ul>
</div>
</th:block>
</div>
</nav>
<main layout:fragment="content" class="d-flex flex-column flex-fill">
</main>
<footer class="my-footer mt-auto d-flex flex-shrink-0 justify-content-center">
Бакальская Е.Д. (@)LiveCinema [[${#dates.year(#dates.createNow())}]]. Все права защищены
</footer>
</body>
</html>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<head>
<title>Вход</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<head>
<title>Вход</title>

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="pagination (url, totalPages, currentPage)">
<nav th:if="${totalPages > 1}" th:with="
maxPage=2,
currentPage=${currentPage + 1}">
<ul class="pagination justify-content-center"
th:with="
seqFrom=${currentPage - maxPage < 1 ? 1 : currentPage - maxPage},
seqTo=${currentPage + maxPage > totalPages ? totalPages : currentPage + maxPage}">
<th:block th:if="${currentPage > maxPage + 1}">
<li class="page-item">
<a class="page-link" aria-label="Previous" th:href="@{/{url}?page=0(url=${url})}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
</th:block>
<li class="page-item" th:each="page : ${#numbers.sequence(seqFrom, seqTo)}"
th:classappend="${page == currentPage} ? 'active' : ''">
<a class="page-link" th:href="@{/{url}?page={page}(url=${url},page=${page - 1})}">
<span th:text="${page}" />
</a>
</li>
<th:block th:if="${currentPage < totalPages - maxPage}">
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
<li class="page-item">
<a class="page-link" aria-label="Next"
th:href="@{/{url}?page={page}(url=${url},page=${totalPages - 1})}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</th:block>
</ul>
</nav>
</th:block>
</body>
</html>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<head>
<title>Регистрация</title>

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<head>
<title>Редакторовать пользователя</title>
</head>
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/admin/user/edit/{id}(id=${user.id},page=${page})}" th:object="${user}"
method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="login" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{login}" id="login" class="form-control">
<div th:if="${#fields.hasErrors('login')}" th:errors="*{login}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{password}" id="password" class="form-control">
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="invalid-feedback"></div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" th:href="@{/admin/user(page=${page})}">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{index}">
<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>
<div>
<a th:href="@{/admin/user/edit/(page=${page})}" class="btn btn-primary">Добавить пользователя</a>
</div>
<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-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${items}">
<th scope="row" th:text="${user.id}"></th>
<td th:text="${user.login}"></td>
<td>
<form th:action="@{/admin/user/edit/{id}(id=${user.id})}" method="get">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/admin/user/delete/{id}(id=${user.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url=${'admin/user'},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</th:block>
</main>
</body>
</html>

Binary file not shown.