Вроде MVC готов (единственное после входа в аккаунт пнг на всю страницу отображает почему то (странно :) ))

This commit is contained in:
Павел Сорокин 2023-05-05 16:43:38 +04:00
parent 6012934a86
commit c30e8182f9
28 changed files with 692 additions and 280 deletions

View File

@ -12,7 +12,7 @@ public class CommentDto {
{
this.id= comment.getId();
this.Text=comment.getText();
this.userName=comment.getUser().getFirstName() + " " + comment.getUser().getLastName();
this.userName=comment.getUser().getLogin();
this.postId=comment.getPost().getId();
}
public Long getId()

View File

@ -0,0 +1,14 @@
package ru.ulstu.is.sbapp.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfiguration {
@Bean
public PasswordEncoder createPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,76 @@
package ru.ulstu.is.sbapp.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import ru.ulstu.is.sbapp.User.controller.UserSignupMvcController;
import ru.ulstu.is.sbapp.User.model.UserRole;
import ru.ulstu.is.sbapp.User.service.UserService;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
securedEnabled = true
)
public class SecurityConfiguration {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private static final String LOGIN_URL = "/login";
private final UserService userService;
public SecurityConfiguration(UserService userService) {
this.userService = userService;
createAdminOnStartup();
}
private void createAdminOnStartup() {
final String admin = "admin";
if (userService.findByLogin(admin) == null) {
log.info("Admin user successfully created");
userService.addUser(admin, "adminemail@gmail.com", admin, admin, UserRole.ADMIN);
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin().and()
.cors().and()
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers(UserSignupMvcController.SIGNUP_URL).permitAll()
.requestMatchers(HttpMethod.GET, LOGIN_URL).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(LOGIN_URL).permitAll()
.and()
.logout().permitAll();
return http.build();
}
@Bean
public AuthenticationManager authenticationManagerBean(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http
.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userService);
return authenticationManagerBuilder.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers("/css/**")
.requestMatchers("/js/**")
.requestMatchers("/templates/**")
.requestMatchers("/webjars/**");
}
}

View File

@ -1,4 +1,4 @@
package ru.ulstu.is.sbapp;
package ru.ulstu.is.sbapp.Configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
@ -11,6 +11,7 @@ public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
registry.addViewController("login");
}
@Override

View File

@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.Comment.model.CommentDto;
import ru.ulstu.is.sbapp.Post.model.PostDto;
import ru.ulstu.is.sbapp.Post.service.PostService;
import ru.ulstu.is.sbapp.WebConfiguration;
import ru.ulstu.is.sbapp.Configuration.WebConfiguration;
import java.util.List;

View File

@ -7,6 +7,7 @@ import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import ru.ulstu.is.sbapp.Comment.model.Comment;
import ru.ulstu.is.sbapp.Comment.model.CommentDto;
import ru.ulstu.is.sbapp.Comment.service.CommentService;
import ru.ulstu.is.sbapp.Post.model.PostDto;
@ -15,10 +16,11 @@ import ru.ulstu.is.sbapp.User.model.UserDto;
import ru.ulstu.is.sbapp.User.service.UserService;
import java.io.IOException;
import java.security.Principal;
import java.util.Base64;
@Controller
@RequestMapping("/post")
@RequestMapping("/index")
public class PostMvcController {
private final PostService postService;
private final UserService userService;
@ -54,7 +56,7 @@ public class PostMvcController {
userService.findAllUsers().stream()
.map(UserDto::new)
.toList());
return "post";
return "index";
}
@GetMapping("/filter")
public String getFileteredPosts(@RequestParam(value = "searchValue") String searchValue,Model model)
@ -67,29 +69,18 @@ public class PostMvcController {
userService.findAllUsers().stream()
.map(UserDto::new)
.toList());
return "post";
return "index";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
@GetMapping(value = {"/edit/{id}"})
public String editPost(@PathVariable(required = false) Long id,
Model model) {
if (id == null || id <= 0) {
model.addAttribute("postDto", new PostDto());
model.addAttribute("users",
userService.findAllUsers().stream()
.map(UserDto::new)
.toList());
return "post-create";
} else {
model.addAttribute("postId", id);
model.addAttribute("postDto", new PostDto(postService.findPost(id)));
return "post-edit";
}
}
@PostMapping(value = {"/user/", "/{id}"})
@PostMapping(value = {"/{id}"})
public String savePost(@PathVariable(required = false) Long id,
@RequestParam(value = "userId",required = false) Long userId,
@RequestParam(value = "multipartFile") MultipartFile multipartFile,
@ModelAttribute @Valid PostDto postDto,
BindingResult bindingResult,
@ -98,27 +89,28 @@ public class PostMvcController {
model.addAttribute("errors", bindingResult.getAllErrors());
return "post-edit";
}
if (id == null || id <= 0 && userId!=null) {
postDto.setImage("data:" + multipartFile.getContentType() + ";base64," + Base64.getEncoder().encodeToString(multipartFile.getBytes()));
userService.addNewPost(userId,postDto);
} else {
postDto.setImage("data:" + multipartFile.getContentType() + ";base64," + Base64.getEncoder().encodeToString(multipartFile.getBytes()));
postService.updatePost(id, postDto);
}
return "redirect:/post";
return "redirect:/user";
}
@PostMapping("/delete/{postId}")
public String deletePost(
@PathVariable Long postId) {
postService.deletePost(postId);
return "redirect:/post";
return "redirect:/user";
}
@PostMapping("/deleteComment/{postId}/{commentId}")
public String deleteComment(@PathVariable Long postId,
@PathVariable Long commentId) {
@PathVariable Long commentId,
Principal principal,Model model) {
Comment comment = commentService.findComment(commentId);
if(!comment.getUser().getLogin().equals(principal.getName())){
model.addAttribute("error", new Exception("Вы не можете удалить не ваш комментарий"));
return "error";
}
postService.removeCommentFromPost(postId,commentId);
return "redirect:/post/{postId}";
return "redirect:/index/{postId}";
}
@GetMapping("/userPosts")
@ -132,48 +124,61 @@ public class PostMvcController {
userService.findAllUsers().stream()
.map(UserDto::new)
.toList());
return "post";
return "index";
}
@GetMapping(value = {"/addComment/{postId}", "/editComment/{id}"})
public String editComment(@PathVariable(required = false) Long id,
@PathVariable(required = false) Long postId,
Model model) {
if (id == null || id <= 0) {
model.addAttribute("postId", postId);
model.addAttribute("users",
userService.findAllUsers().stream()
.map(UserDto::new)
.toList());
model.addAttribute("commentDto", new CommentDto());
return "comment-create.html";
} else {
model.addAttribute("id", id);
model.addAttribute("commentDto", new CommentDto(commentService.findComment(id)));
@GetMapping("/addComment/{postId}")
public String showCreateCommentInfo(@PathVariable(value = "postId") Long postId, Model model) {
model.addAttribute("postId", postId);
model.addAttribute("commentDto", new CommentDto());
return "comment-create.html";
}
@PostMapping("/createComment/{postId}")
public String createPost(@PathVariable(value = "postId") Long postId,
@ModelAttribute @Valid CommentDto commentDto,
BindingResult bindingResult,
Principal principal,
Model model) throws IOException
{
Long userId = userService.findByLogin(principal.getName()).getId();
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "post-create";
}
postService.addCommentToPost(postId,userId,commentDto.getText());
return "redirect:/index";
}
@GetMapping("/editComment/{Id}")
public String showEditCommentInfo(@PathVariable(value = "Id") Long Id, Model model,Principal principal) throws Exception {
model.addAttribute("Id", Id);
CommentDto commentDto = new CommentDto(commentService.findComment(Id));
model.addAttribute("commentDto",commentDto);
if(!commentDto.getUser().equals(principal.getName())){
model.addAttribute("error", new Exception("Вы не можете изменить не ваш комментарий"));
return "error";
}
else
{
return "comment-edit";
}
}
@PostMapping(value = {"/comment/{postId}/user", "/comment/{id}"})
public String saveComment(@PathVariable(required = false) Long id,
@RequestParam(value = "userId",required = false) Long userId,
@PathVariable(required = false) Long postId,
@ModelAttribute @Valid CommentDto commentDto,
BindingResult bindingResult,
Model model,
HttpServletRequest request) {
@PostMapping("/updateComment/{Id}")
public String updateComment(@PathVariable(value = "Id") Long Id,
@ModelAttribute @Valid CommentDto commentDto,
BindingResult bindingResult,
Principal principal,
Model model) throws IOException
{
Long userId = userService.findByLogin(principal.getName()).getId();
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "comment-edit";
}
if (id == null || id <= 0 && userId!=null) {
postService.addCommentToPost(postId,userId,commentDto.getText());
return "redirect:/post/"+ postId;
} else {
commentService.updateComment(id, commentDto.getText());
return "redirect:/post";
}
commentService.updateComment(Id,commentDto.getText());
return "redirect:/user";
}
}

View File

@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.Post.model.PostDto;
import ru.ulstu.is.sbapp.User.model.UserDto;
import ru.ulstu.is.sbapp.User.service.UserService;
import ru.ulstu.is.sbapp.WebConfiguration;
import ru.ulstu.is.sbapp.Configuration.WebConfiguration;
import java.util.List;
@ -42,11 +42,10 @@ public class UserController {
@PutMapping("/{id}")
public UserDto updateClient(@PathVariable Long id,
@RequestParam("firstName") String firstName,
@RequestParam("lastName") String lastname,
@RequestParam("firstName") String login,
@RequestParam("email") String email,
@RequestParam("password") String password){
return new UserDto(userService.updateUser(id, firstName, lastname,email,password));
return new UserDto(userService.updateUser(id, login,email,password));
}
@PostMapping("/{id}/Post")
public void addPost(@PathVariable Long id,

View File

@ -1,13 +1,31 @@
package ru.ulstu.is.sbapp.User.controller;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import org.springframework.data.domain.Page;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import ru.ulstu.is.sbapp.Comment.model.CommentDto;
import ru.ulstu.is.sbapp.Post.model.PostDto;
import ru.ulstu.is.sbapp.User.model.User;
import ru.ulstu.is.sbapp.User.model.UserDto;
import ru.ulstu.is.sbapp.User.model.UserRole;
import ru.ulstu.is.sbapp.User.service.UserService;
import java.io.IOException;
import java.security.Principal;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import java.util.stream.IntStream;
@Controller
@RequestMapping("/user")
public class UserMvcController {
@ -16,45 +34,82 @@ public class UserMvcController {
{
this.userService=userService;
}
@GetMapping(value = "/all")
@Secured({UserRole.AsString.ADMIN})
public String getUsers(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "5") int size,
Principal principal, Model model) {
final Page<UserDto> users = userService.findAllPages(page, size)
.map(UserDto::new);
model.addAttribute("users", users);
final int totalPages = users.getTotalPages();
final List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
.boxed()
.toList();
model.addAttribute("pages", pageNumbers);
model.addAttribute("totalPages", totalPages);
return "users";
}
@GetMapping
public String getUsers(Model model) {
model.addAttribute("users",
userService.findAllUsers().stream()
.map(UserDto::new)
.toList());
public String showUpdateUserForm(Principal principal, Model model) {
UserDto userDto = new UserDto(userService.findByLogin(principal.getName()));
Long id = userService.findByLogin(principal.getName()).getId();
model.addAttribute("userId",id);
model.addAttribute("userDto", userDto);
model.addAttribute("posts",userService.GetUserPosts(id).stream()
.map(PostDto::new)
.toList());
return "user";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editUser(@PathVariable(required = false) Long id,
Model model) {
if (id == null || id <= 0) {
model.addAttribute("userDto", new UserDto());
} else {
model.addAttribute("userId", id);
model.addAttribute("userDto", new UserDto(userService.findUser(id)));
}
return "user-edit";
}
@PostMapping(value = {"/", "/{id}"})
public String saveUser(@PathVariable(required = false) Long id,
@ModelAttribute @Valid UserDto userDto,
BindingResult bindingResult,
Model model) {
@PostMapping
public String updateUser(@ModelAttribute @Valid UserDto userDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "user-edit";
return "user";
}
if (id == null || id <= 0) {
userService.addUser(userDto.getFirstName(), userDto.getLastName(),userDto.getEmail(),userDto.getPassword());
} else {
userService.updateUser(id, userDto.getFirstName(), userDto.getLastName(),userDto.getEmail(),userDto.getPassword());
try {
userService.updateUser(userDto);
@SuppressWarnings("unchecked")
Collection<SimpleGrantedAuthority> nowAuthorities =
(Collection<SimpleGrantedAuthority>)SecurityContextHolder.getContext()
.getAuthentication()
.getAuthorities();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDto.getLogin(), userDto.getPassword(), nowAuthorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (ValidationException e) {
model.addAttribute("errors", e.getMessage());
}
return "redirect:/user";
return "user";
}
@GetMapping("/post")
public String showCreatePostInfo(Principal principal, Model model) {
Long id = userService.findByLogin(principal.getName()).getId();
model.addAttribute("userId",id);
model.addAttribute("postDto", new PostDto());
return "post-create";
}
@PostMapping("/createPost/{userId}")
public String createPost(@PathVariable(value = "userId") Long id,
@ModelAttribute @Valid PostDto postDto,
@RequestParam(value = "multipartFile") MultipartFile multipartFile,
BindingResult bindingResult,
Model model) throws IOException
{
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "post-create";
}
postDto.setImage("data:" + multipartFile.getContentType() + ";base64," + Base64.getEncoder().encodeToString(multipartFile.getBytes()));
userService.addNewPost(id,postDto);
return "redirect:/user";
}
@PostMapping("/delete/{id}")
@Secured({UserRole.AsString.ADMIN})
public String deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return "redirect:/user";

View File

@ -0,0 +1,51 @@
package ru.ulstu.is.sbapp.User.controller;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import ru.ulstu.is.sbapp.User.model.User;
import ru.ulstu.is.sbapp.User.model.UserRole;
import ru.ulstu.is.sbapp.User.model.UserSignupDto;
import ru.ulstu.is.sbapp.User.service.UserService;
@Controller
@RequestMapping(UserSignupMvcController.SIGNUP_URL)
public class UserSignupMvcController
{
public static final String SIGNUP_URL = "/signup";
private final UserService userService;
public UserSignupMvcController(UserService userService) {
this.userService = userService;
}
@GetMapping
public String showSignupForm(Model model) {
model.addAttribute("userDto", new UserSignupDto());
return "signup";
}
@PostMapping
public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "signup";
}
try {
final User user = userService.addUser(userSignupDto.getLogin(),userSignupDto.getEmail(),
userSignupDto.getPassword(), userSignupDto.getPasswordConfirm(), UserRole.USER);
return "redirect:/login?created=" + user.getLogin();
} catch (ValidationException e) {
model.addAttribute("errors", e.getMessage());
return "signup";
}
}
}

View File

@ -2,6 +2,7 @@ package ru.ulstu.is.sbapp.User.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import ru.ulstu.is.sbapp.Comment.model.Comment;
import ru.ulstu.is.sbapp.Post.model.Post;
@ -16,14 +17,12 @@ public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column()
@NotBlank(message = "firstName cannot be null")
private String firstName;
@NotBlank(message = "lastName cannot be null")
private String lastName;
@NotBlank(message = "Login can't be null or empty")
@Size(min = 3, max = 64, message = "Incorrect login length")
private String login;
@Column(nullable = false, unique = true, length = 64)
@NotBlank(message = "email cannot be null")
@Pattern(regexp = "^(.+)@(\\S+)$", message = "Incorrect email value")
private String email;
@Column(nullable = false, length = 64)
@ -41,37 +40,26 @@ public class User {
public User() {
}
public User(String firstName,String lastName,String email, String password) {
this(firstName,lastName,email, password, UserRole.USER);
public User(String login,String email, String password) {
this(login,email, password, UserRole.USER);
}
public User(String firstName, String lastName, String email,String password,UserRole role) {
this.firstName = firstName;
this.lastName = lastName;
public User(String login,String email,String password,UserRole role) {
this.login=login;
this.email=email;
this.password=password;
this.role=role;
}
public User(UserSignupDto userSignupDto) {
this.login = userSignupDto.getLogin();
this.email = userSignupDto.getEmail();
this.password = userSignupDto.getPassword();
this.role = UserRole.USER;
}
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List<Post> getPosts()
{
return posts;
@ -112,6 +100,14 @@ public class User {
return Objects.equals(id, user.id);
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
@Override
public int hashCode() {
return Objects.hash(id);
@ -121,8 +117,7 @@ public class User {
public String toString() {
return "Client{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", login='" + login + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
", posts=" + posts +'\''+

View File

@ -10,9 +10,7 @@ import java.util.List;
public class UserDto {
private Long id;
private String firstName;
private String lastName;
private String login;
private String email;
@ -27,8 +25,7 @@ public class UserDto {
public UserDto(){}
public UserDto(User user) {
this.id=user.getId();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.login = user.getLogin();
this.email= user.getEmail();
this.role=user.getRole();
this.password=user.getPassword();
@ -37,12 +34,8 @@ public class UserDto {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
public String getLogin() {
return login;
}
public String getPassword(){
@ -78,13 +71,10 @@ public class UserDto {
this.id = id;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
public void setLogin(String login) {
this.login = login;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setComments(List<Comment> comments) {
this.comments = comments;

View File

@ -0,0 +1,40 @@
package ru.ulstu.is.sbapp.User.model;
public class UserSignupDto {
private String login;
private String email;
private String password;
private String passwordConfirm;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}

View File

@ -12,4 +12,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
@Query("Select p from Post p where user.id = :id")
List<Post> getUsersPosts(Long id);
User findOneByLoginIgnoreCase(String login);
}

View File

@ -1,5 +1,12 @@
package ru.ulstu.is.sbapp.User.service;
import jakarta.validation.ValidationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.is.sbapp.Comment.service.CommentService;
@ -7,40 +14,72 @@ import ru.ulstu.is.sbapp.Post.model.PostDto;
import ru.ulstu.is.sbapp.Post.model.Post;
import ru.ulstu.is.sbapp.Post.repository.PostRepository;
import ru.ulstu.is.sbapp.User.model.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import ru.ulstu.is.sbapp.User.model.UserDto;
import ru.ulstu.is.sbapp.User.model.UserRole;
import ru.ulstu.is.sbapp.User.model.UserSignupDto;
import ru.ulstu.is.sbapp.User.repository.UserRepository;
import ru.ulstu.is.sbapp.Util.validation.ValidatorUtil;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@Service
public class UserService {
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PostRepository postRepository;
private final CommentService commentService;
private final PasswordEncoder passwordEncoder;
private final ValidatorUtil validatorUtil;
public UserService(UserRepository userRepository, ValidatorUtil validatorUtil, PostRepository postRepository, CommentService commentService)
public UserService(UserRepository userRepository, ValidatorUtil validatorUtil, PostRepository postRepository, CommentService commentService,PasswordEncoder passwordEncoder)
{
this.userRepository=userRepository;
this.validatorUtil=validatorUtil;
this.postRepository = postRepository;
this.commentService = commentService;
this.passwordEncoder=passwordEncoder;
}
public User findByLogin(String login) {
return userRepository.findOneByLoginIgnoreCase(login);
}
public Page<User> findAllPages(int page, int size) {
return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending()));
}
public User addUser(String firstName, String lastName, String email, String password) {
return addUser(firstName,lastName, email, password, UserRole.USER);
}
@Transactional
public User addUser(String firstName, String lastName, String email, String password, UserRole role) {
final User user = new User(firstName, lastName, email,password,role);
public User addUser(String login, String email, String password,String passwordConfirm, UserRole role) {
if (findByLogin(login) != null) {
throw new ValidationException(String.format("User '%s' already exists", login));
}
if (!Objects.equals(password, passwordConfirm)) {
throw new ValidationException("Passwords not equals");
}
final User user = new User(login,email,passwordEncoder.encode(password),role);
validatorUtil.validate(user);
return userRepository.save(user);
}
@Transactional
public User addUser(UserSignupDto userSignupDto) {
if (findByLogin(userSignupDto.getLogin()) != null) {
throw new ValidationException(String.format("User '%s' already exists", userSignupDto.getLogin()));
}
if (!Objects.equals(userSignupDto.getPassword(), userSignupDto.getPasswordConfirm())) {
throw new ValidationException("Passwords not equals");
}
final User user = new User(userSignupDto);
validatorUtil.validate(user);
return userRepository.save(user);
}
@Transactional
public User findUser(Long id) {
@ -54,15 +93,29 @@ public class UserService {
}
@Transactional
public User updateUser(Long id, String firstName, String lastName, String email,String password) {
public User updateUser(Long id, String firstName, String email,String password) {
final User currentUser = findUser(id);
currentUser.setFirstName(firstName);
currentUser.setLastName(lastName);
currentUser.setLogin(firstName);
currentUser.setEmail(email);
currentUser.setPassword(password);
validatorUtil.validate(currentUser);
return userRepository.save(currentUser);
}
@Transactional
public User updateUser(UserDto userDto) {
final User currentUser = findUser(userDto.getId());
final User sameUser = findByLogin(userDto.getLogin());
if (sameUser != null && !Objects.equals(sameUser.getId(), currentUser.getId())) {
throw new ValidationException(String.format("User '%s' already exists", userDto.getLogin()));
}
if (!passwordEncoder.matches(userDto.getPassword(), currentUser.getPassword())) {
throw new ValidationException("Incorrect password");
}
currentUser.setLogin(userDto.getLogin());
currentUser.setEmail(userDto.getEmail());
validatorUtil.validate(currentUser);
return userRepository.save(currentUser);
}
@Transactional
public void deleteUser(Long id) {
@ -98,4 +151,14 @@ public class UserService {
public void deletePost(Long id, Long postId) {
postRepository.deleteById(postId);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final User userEntity = findByLogin(username);
if (userEntity == null) {
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(
userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()));
}
}

View File

@ -8,18 +8,10 @@
<div layout:fragment="content">
<div class="mx-auto my-3">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{'/post/comment/' + ${postId} + '/user'}" th:object="${commentDto}" method="post">
<div class="mb-3 my-2">
<label for="selectBox" class="form-label">Пользователь</label>
<select th:name="userId" id="selectBox" class="form-select">
<option th:each="value: ${users}" th:selected="${selectBox} == ${value}" th:value="${value.id}">
<span th:text="${value.firstName}"></span>
</option>
</select>
</div>
<form action="#" th:action="@{'/index/createComment/' + ${postId}}" th:object="${commentDto}" method="post">
<div class="mb-3">
<div>
<input class="form-control" id="commentText" th:field="${commentDto.text}" type="text" defaultValue="" required/>
<input class="form-control" id="commentText" th:field="${commentDto.text}" type="text" defaultValue="" required/>
</div>
<div>
<button type="submit" class="btn btn-primary button-fixed">

View File

@ -8,7 +8,7 @@
<div layout:fragment="content">
<div class="mx-auto my-3">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/post/comment/{id}(id=${id})}" th:object="${commentDto}" method="post">
<form action="#" th:action="@{/index/updateComment/{Id}(Id=${Id})}" th:object="${commentDto}" method="post">
<div class="mb-3">
<div>
<input class="form-control" id="commentText" th:field="${commentDto.text}" type="text" defaultValue="" required/>

View File

@ -1,6 +1,6 @@
<html lang="ru"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="">
<head>
<meta charset="UTF-8"/>
<title>Школа</title>
@ -9,14 +9,14 @@
<script type="text/javascript" src="/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.1.3/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/webjars/font-awesome/6.1.0/css/all.min.css"/>
<link rel="stylesheet" href="/style.css"/>
<link rel="stylesheet" href="/css/style.css"/>
</head>
<body class="body_app">
<header class="fs-4 fw-bold p-1 text-white bg-primary bg-gradient">
<div><img class="img-fluid float-start" src="/img/Emblema.png" alt="Emblema"/>
<p class="fs-5 Cont">Муниципальное бюджетное общеобразовательное учреждение средняя общеобразовательная школа №10</p>
</div>
<nav class="navbar navbar-expand-md navbar-dark">
<nav class="navbar navbar-expand-md navbar-dark" sec:authorize="isAuthenticated()">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span></button>
<nav class="headers-problem navbar navbar-expand-lg d-flex">
@ -27,8 +27,15 @@
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<a class="btn btn-outline-light mx-1" href="/user"> Авторизация </a>
<a class="btn btn-outline-light mx-1" href="/post">Новости</a>
<a class="btn btn-outline-light mx-1" href="/user/all" sec:authorize="hasRole('ROLE_ADMIN')"
th:classappend="${#strings.equals(activeLink, '/user')} ? 'active' : ''">
Пользователи
</a>
<a class="btn btn-outline-light mx-1" href="/user"
th:classappend="${#strings.equals(activeLink, '/user')} ? 'active' : ''">
Профиль
</a>
<a class="btn btn-outline-light mx-1" href="/index">Новости</a>
</ul>
</div>
</nav>

View File

@ -7,7 +7,7 @@
<body>
<div layout:fragment="content">
<div><span th:text="${error}"></span></div>
<a href="/">На главную</a>
<a href="/index">На главную</a>
</div>
</body>
</html>

View File

@ -9,28 +9,21 @@
<div layout:fragment="content">
<div class="d-flex float-start my-1">
<form th:action="@{/post/filter}" class="d-flex">
<form th:action="@{/index/filter}" class="d-flex">
<div class="mx-3"><input type="text" th:name="searchValue" id="search" class="form-control" required /></div>
<button type="submit" class="btn btn-outline-primary text-center mx-2">
<i class="fa-solid fa-magnifying-glass"></i>
</button>
</form>
<form th:action="@{/post/userPosts}" id="myForm" class="d-flex">
<form th:action="@{/index/userPosts}" id="myForm" class="d-flex">
<div class="mx-3">
<select id="selectBox" th:name="userId">
<option value="" disabled selected>Select your option</option>
<option th:each="value: ${users}" th:selected="${selectBox} == ${value}" th:text="${value.firstName}" th:value="${value.Id}">
<option th:each="value: ${users}" th:selected="${selectBox} == ${value}" th:text="${value.login}" th:value="${value.Id}">
</option>
</select>
</div>
</form>
<div>
<a class="btn btn-outline-primary text-center mx-2"
th:href="@{/post/edit}">
<!--Сам понимаешь что здесь надо сделать-->
+
</a>
</div>
</div>
<div class="my-5">
<!--здесь тоже надо сделать нормальный вывод-->
@ -66,20 +59,7 @@
</table>
</div>
<div class="d-flex flex-row justify-content-end ">
<a class="btn btn-outline-primary text-center mx-2"
th:href="@{/post/edit/{id}(id=${post.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-outline-primary text-center mx-2"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${post.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
<form th:action="@{'/post/delete/' + ${post.id}}" method="post">
<button th:id="'remove-' + ${post.id}" type="submit" style="display: none">
Удалить
</button>
</form>
<a th:href=@{/post/{id}(id=${post.id})} class='btn btn-outline-primary mx-2'><i class="fa-solid fa-envelopes-bulk"></i></a>
<a th:href=@{/index/{id}(id=${post.id})} class='btn btn-outline-primary mx-2'><i class="fa-solid fa-envelopes-bulk"></i></a>
</div>
</div>
</div>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<div layout:fragment="content">
<div th:if="${param.error}" class="alert alert-danger margin-bottom">
User not found
</div>
<div th:if="${param.logout}" class="alert alert-success margin-bottom">
Logout success
</div>
<div th:if="${param.created}" class="alert alert-success margin-bottom">
User '<span th:text="${param.created}"></span>' was successfully created
</div>
<form th:action="@{/login}" method="post">
<div class="mb-3">
<p class="mb-1">Login</p>
<input name="username" id="username" class="form-control"
type="text" required autofocus />
</div>
<div class="mb-3">
<p class="mb-1">Password</p>
<input name="password" id="password" class="form-control"
type="password" required />
</div>
<div class="mb-3">
<button type="submit" class="btn btn-success">
Sing in
</button>
</div>
<div>
<p>
<span>Not a member yet?</span>
<a href="/signup">Sing Up here</a>
</p>
</div>
</form>
</div>
</body>
</html>

View File

@ -7,15 +7,7 @@
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/post/user/}" th:object="${postDto}" method="post" enctype="multipart/form-data">
<div class="mb-3 my-2">
<label for="selectBox" class="form-label">Пользователь</label>
<select th:name="userId" id="selectBox" class="form-select">
<option th:each="value: ${users}" th:selected="${selectBox} == ${value}" th:value="${value.id}">
<span th:text="${value.firstName}"></span>
</option>
</select>
</div>
<form action="#" th:action="@{/user/createPost/{userId}(userId=${userId})}" th:object="${postDto}" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="image" class="form-label">Картинка</label>
<input type="file" th:name="multipartFile" class="form-control" id="image" required="true">

View File

@ -7,7 +7,7 @@
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/post/{id}(id=${id})}" th:object="${postDto}" method="post" enctype="multipart/form-data">
<form action="#" th:action="@{/index/{id}(id=${id})}" th:object="${postDto}" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="image" class="form-label">Картинка</label>
<input type="file" th:name="multipartFile" class="form-control" id="image" required="true">

View File

@ -16,7 +16,7 @@
</div>
<!--Вот тут кнопка для добавления комментариев (для будущего тебя)-->
<a class="btn btn-outline-primary text-center mx-2"
th:href="@{/post/addComment/{postId}(postId=${post.id})}">
th:href="@{/index/addComment/{postId}(postId=${post.id})}">
+
</a>
<div className="d-flex mx-2">
@ -38,7 +38,7 @@
<td style="width: 10%">
<div class="btn-group" role="group" aria-label="Basic example">
<!--НУЖНО СДЕЛАТЬ!!!-->
<a class="btn btn-outline-light text-center mx-2" th:href="@{/post/editComment/{id}(id=${comment.id})}">
<a class="btn btn-outline-light text-center mx-2" th:href="@{/index/editComment/{Id}(Id=${comment.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-outline-light text-center mx-2"
@ -46,7 +46,7 @@
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
</div>
<form th:action="@{'/post/deleteComment/' + ${comment.postId} + '/' + ${comment.id}}" method="post">
<form th:action="@{'/index/deleteComment/' + ${comment.postId} + '/' + ${comment.id}}" method="post">
<button th:id="'remove-' + ${comment.id}" type="submit" style="display: none">
Удалить
</button>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<div layout:fragment="content">
<div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
<form action="#" th:action="@{/signup}" th:object="${userDto}" method="post">
<div class="mb-3">
<label for="login" class="mb-1">Login</label>
<input id="login" th:field="${userDto.login}" class="form-control"
type="text" required maxlength="64" />
</div>
<div class="mb-3">
<label for="email" class="mb-1">Email</label>
<input id="email" th:field="${userDto.email}" class="form-control"
type="text" required />
</div>
<div class="mb-3">
<label for="password" class="mb-1">Password</label>
<input id="password" th:field="${userDto.password}" class="form-control"
type="password" required minlength="3" maxlength="64" />
</div>
<div class="mb-3">
<label for="passwordConfirm" class="mb-1">Confirm Password</label>
<input id="passwordConfirm" th:field="${userDto.passwordConfirm}" class="form-control"
type="password" required minlength="3" maxlength="64" />
</div>
<div class="mb-3">
<button type="submit" class="btn btn-success">
Create account
</button>
</div>
<div>
<p>
<span>Already have an account?</span>
<a href="/login">Sing In here</a>
</p>
</div>
</form>
</div>
</body>
</html>

View File

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/user/{id}(id=${id})}" th:object="${userDto}" method="post">
<div class="mb-3">
<label for="firstName" class="form-label">Имя</label>
<input type="text" class="form-control" id="firstName" th:field="${userDto.firstName}" required="true">
</div>
<div class="mb-3">
<label for="lastName" class="form-label">Фамилия</label>
<input type="text" class="form-control" id="lastName" th:field="${userDto.lastName}" required="true">
</div>
<div class="mb-3">
<label for="email" class="form-label">Почта</label>
<input type="text" class="form-control" id="email" th:field="${userDto.email}" required="true">
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="text" class="form-control" id="password" th:field="${userDto.password}" required="true">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span th:if="${id == null}">Добавить</span>
<span th:if="${id != null}">Обновить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/user}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@ -1,57 +1,108 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<html lang="en" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}"
xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div>
<a class="btn btn-outline-light text-center mx-2 my-2"
th:href="@{/user/edit}">
<i class="fa-solid fa-plus"></i>
<div class="border-bottom pb-3 mb-3 mx-2">
<a class="btn btn-primary" href="/logout">
Log Out
</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">ID</th>
<th scope="col">Имя</th>
<th scope="col">Почта</th>
<th scope="col">Пароль</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="user, iterator: ${users}">
<th scope="row" th:text="${iterator.index} + 1"/>
<td th:text="${user.id}"/>
<td th:text="${user.firstName} + ' ' + ${user.lastName}" style="width: 60%"/>
<td th:text="${user.email}" style="width: 60%"/>
<td th:text="${user.password}" style="width: 60%"/>
<td style="width: 10%">
<div class="btn-group" role="group" aria-label="Basic example">
<a class="btn btn-outline-light text-center mx-2"
th:href="@{/user/edit/{id}(id=${user.id})}">
<h4>Данные профиля</h4>
<div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
<form action="/user" th:object="${userDto}" method="post">
<input th:field="${userDto.id}" type="number" style="display: none;" />
<div class="mb-3">
<label for="login" class="form-label">Логин</label>
<input id="login" th:field="${userDto.login}" class="form-control"
required type="text" />
</div>
<div class="mb-3">
<label for="email" class="form-label">Почта</label>
<input id="email" th:field="${userDto.email}" class="form-control"
required type="text" />
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль (введите для подтверждения изменения данных)</label>
<input id="password" th:field="${userDto.password}" class="form-control"
required type="password" />
</div>
<div class="mb-3">
<button type="submit" class="btn btn-success">
Изменить
</button>
</div>
</form>
<div>
<a class="btn btn-outline-primary text-center mx-2"
th:href="@{/user/post}">
<!--Сам понимаешь что здесь надо сделать-->
+
</a>
</div>
<div class="my-5">
<!--здесь тоже надо сделать нормальный вывод-->
<div class="container-fluid">
<div th:each="post, iterator: ${posts}" class="card border-dark mb-3 d-flex flex-row text-black justify-content-between">
<div>
<img class="col" style="width:300px;height:200px" th:src="${post.image}"/>
</div>
<div class="d-flex flex-grow-1 flex-column">
<div class="flex-grow-1 mx-2">
<h th:text="${post.heading}"></h>
</div>
<div class="flex-grow-1 mx-2">
<p th:text="${post.content}"></p>
</div>
<div class="flex-grow-1 mx-2">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Пользователь</th>
<th scope="col">Текст</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="comment, iterator: ${post.comments}">
<th scope="row" th:text="${iterator.index} + 1"/>
<td th:text="${comment.user}" style="width: 60%"/>
<td th:text="${comment.text}" style="width: 60%"/>
</tr>
</tbody>
</table>
</div>
<div class="d-flex flex-row justify-content-end ">
<a class="btn btn-outline-primary text-center mx-2"
th:href="@{/index/edit/{id}(id=${post.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-outline-light text-center mx-2"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${user.id}').click()|">
<button type="button" class="btn btn-outline-primary text-center mx-2"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${post.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
<form th:action="@{'/index/delete/' + ${post.id}}" method="post">
<button th:id="'remove-' + ${post.id}" type="submit" style="display: none">
Удалить
</button>
</form>
<a th:href=@{/index/{id}(id=${post.id})} class='btn btn-outline-primary mx-2'><i class="fa-solid fa-envelopes-bulk"></i></a>
</div>
<form th:action="@{/user/delete/{id}(id=${user.id})}" method="post">
<button th:id="'remove-' + ${user.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<th:block layout:fragment="scripts">
<script type="module">
</script>
</th:block>
</html>

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">ID</th>
<th scope="col">Логин</th>
<th scope="col">Почта</th>
<th scope="col">Пароль</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="user, iterator: ${users}">
<th scope="row" th:text="${iterator.index} + 1"/>
<td th:text="${user.id}"/>
<td th:text="${user.login}" style="width: 60%"/>
<td th:text="${user.email}" style="width: 60%"/>
<td style="width: 10%">
<div class="btn-group" role="group" aria-label="Basic example">
<button type="button" class="btn btn-outline-light text-center mx-2"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${user.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
</div>
<form th:action="@{/user/delete/{id}(id=${user.id})}" method="post">
<button th:id="'remove-' + ${user.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
<div th:if="${totalPages > 0}" class="pagination">
<span style="float: left; padding: 5px 5px;">Pages:</span>
<a th:each="page : ${pages}"
th:href="@{/user/all(page=${page}, size=${users.size})}"
th:text="${page}"
th:class="${page == users.number + 1} ? active">
</a>
</div>
</div>
</div>
</body>
</html>