diff --git a/SpringApp/library/build.gradle b/SpringApp/library/build.gradle index 8b9b426..8ca7ab5 100644 --- a/SpringApp/library/build.gradle +++ b/SpringApp/library/build.gradle @@ -34,11 +34,15 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'com.h2database:h2:2.2.224' + + implementation 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0' + runtimeOnly 'org.webjars.npm:bootstrap:5.3.3' + runtimeOnly 'org.webjars.npm:bootstrap-icons:1.11.3' implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'com.auth0:java-jwt:4.4.0' - - annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/authors/AuthorController.java b/SpringApp/library/src/main/java/com/ip/library/controllers/authors/AuthorController.java index 7cbf060..05b1905 100644 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/authors/AuthorController.java +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/authors/AuthorController.java @@ -1,28 +1,28 @@ package com.ip.library.controllers.authors; -import java.util.List; - import org.modelmapper.ModelMapper; -import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.ip.library.controllers.users.UserRole; import com.ip.library.core.configuration.Constants; import jakarta.validation.Valid; @RestController -@Secured(value = UserRole.Secured.ADMIN) -@RequestMapping(Constants.API_URL + "/author") +@RequestMapping(AuthorController.URL) public class AuthorController { + public static final String URL = Constants.API_URL + "/author"; + private static final String AUTHOR_VIEW = "author"; + private static final String AUTHOR_EDIT_VIEW = "author-edit"; + private static final String AUTHOR_ATTRIBUTE = "author"; + private final AuthorService authorService; private final ModelMapper modelMapper; @@ -40,27 +40,66 @@ public class AuthorController { } @GetMapping - public List getAll() { - return authorService.getAll().stream().map(this::toDto).toList(); + public String getAll(Model model) { + model.addAttribute( + "items", + authorService + .getAll() + .stream() + .map(this::toDto) + .toList()); + return AUTHOR_VIEW; } - @GetMapping("/{id}") - public AuthorDto get(@PathVariable(name = "id") Long id) { - return toDto(authorService.get(id)); + @GetMapping("/edit/") + public String create(Model model) { + model.addAttribute(AUTHOR_ATTRIBUTE, new AuthorDto()); + return AUTHOR_EDIT_VIEW; } - @PostMapping - public AuthorDto create(@RequestBody @Valid AuthorDto dto) { - return toDto(authorService.create(toEntity(dto))); + @PostMapping("/edit/") + public String create( + @ModelAttribute(name = AUTHOR_ATTRIBUTE) @Valid AuthorDto author, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return AUTHOR_EDIT_VIEW; + } + authorService.create(toEntity(author)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public AuthorDto update(@PathVariable(name = "id") Long id, @RequestBody AuthorDto dto) { - return toDto(authorService.update(id, toEntity(dto))); + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(AUTHOR_ATTRIBUTE, toDto(authorService.get(id))); + return AUTHOR_EDIT_VIEW; } - @DeleteMapping("/{id}") - public AuthorDto delete(@PathVariable(name = "id") Long id) { - return toDto(authorService.delete(id)); + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @ModelAttribute(name = AUTHOR_ATTRIBUTE) @Valid AuthorDto author, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return AUTHOR_EDIT_VIEW; + } + if (id <= 0) { + throw new IllegalArgumentException(); + } + authorService.update(id, toEntity(author)); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/delete/{id}") + public String delete( + @PathVariable(name = "id") Long id) { + authorService.delete(id); + return Constants.REDIRECT_VIEW + URL; } } diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/books/BookController.java b/SpringApp/library/src/main/java/com/ip/library/controllers/books/BookController.java index 2ec3bab..8048ef3 100644 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/books/BookController.java +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/books/BookController.java @@ -1,29 +1,36 @@ package com.ip.library.controllers.books; -import java.util.List; +import java.util.Map; import org.modelmapper.ModelMapper; -import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import com.ip.library.controllers.types.TypeService; -import com.ip.library.controllers.users.UserRole; +import com.ip.library.core.api.PageAttributesMapper; import com.ip.library.core.configuration.Constants; import jakarta.validation.Valid; @RestController -@Secured(value = UserRole.Secured.ADMIN) -@RequestMapping(Constants.API_URL + "/book") +@RequestMapping(BookController.URL) public class BookController { + public static final String URL = Constants.API_URL + "/book"; + private static final String BOOK_VIEW = "book"; + private static final String BOOK_EDIT_VIEW = "book-edit"; + private static final String BOOK_ATTRIBUTE = "book"; + private static final String PAGE_ATTRIBUTE = "page"; + private static final String AUTHOR_ATTRIBUTE = "authorId"; + private static final String TYPE_ATTRIBUTE = "typeId"; + private final BookService bookService; private final TypeService typeService; private final ModelMapper modelMapper; @@ -47,44 +54,83 @@ public class BookController { } @GetMapping - public List getAll( - @RequestParam(name = "typeId", defaultValue = "-1") Long typeId, - @RequestParam(name = "authorId", defaultValue = "-1") Long authorId, - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { - return bookService.getAll(typeId, authorId, page, size).stream().map(this::toBookDto).toList(); + public String getAll( + @RequestParam(name = TYPE_ATTRIBUTE, defaultValue = "-1") Long typeId, + @RequestParam(name = AUTHOR_ATTRIBUTE, defaultValue = "-1") Long authorId, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + final Map attributes = PageAttributesMapper.toAttributes( + bookService.getAll(typeId, authorId, page, Constants.DEFUALT_PAGE_SIZE), this::toBookDto); + model.addAllAttributes(attributes); + model.addAttribute(PAGE_ATTRIBUTE, page); + return BOOK_VIEW; } - @GetMapping("/{id}") - public BookDto get(@PathVariable(name = "id") Long id) { - return toBookDto(bookService.get(id)); + @GetMapping("/edit/") + public String create( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + model.addAttribute(BOOK_ATTRIBUTE, new BookDto()); + model.addAttribute(PAGE_ATTRIBUTE, page); + return BOOK_EDIT_VIEW; } - @PostMapping - public BookDto create(@RequestBody @Valid BookDto dto) { - return toBookDto(bookService.create(toEntity(dto))); + @PostMapping("/edit/") + public String create( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + @ModelAttribute(name = BOOK_ATTRIBUTE) @Valid BookDto book, + BindingResult bindingResult, + Model model, + RedirectAttributes redirectAttributes) { + if (bindingResult.hasErrors()) { + model.addAttribute(PAGE_ATTRIBUTE, page); + return BOOK_EDIT_VIEW; + } + redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page); + bookService.create(toEntity(book)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public BookDto update(@PathVariable(name = "id") Long id, @RequestBody BookDto dto) { - return toBookDto(bookService.update(id, toEntity(dto))); + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(BOOK_ATTRIBUTE, toBookDto(bookService.get(id))); + model.addAttribute(PAGE_ATTRIBUTE, page); + return BOOK_EDIT_VIEW; } - @DeleteMapping("/{id}") - public BookDto delete(@PathVariable(name = "id") Long id) { - return toBookDto(bookService.delete(id)); + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + @ModelAttribute(name = BOOK_ATTRIBUTE) @Valid BookDto book, + BindingResult bindingResult, + Model model, + RedirectAttributes redirectAttributes) { + if (bindingResult.hasErrors()) { + model.addAttribute(PAGE_ATTRIBUTE, page); + return BOOK_EDIT_VIEW; + } + if (id <= 0) { + throw new IllegalArgumentException(); + } + redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page); + bookService.update(id, toEntity(book)); + return Constants.REDIRECT_VIEW + URL; } - @GetMapping("/{bookId}/author/{authorId}") - public boolean addAuthor( - @PathVariable(name = "bookId") Long bookId, - @PathVariable(name = "authorId") Long authorId) { - return bookService.addAuthor(authorId, bookId); - } - - @Secured(value = { UserRole.Secured.USER, UserRole.Secured.ADMIN }) - @GetMapping("/{bookId}/number") - public int getBookSubscribersNumber(@PathVariable(name = "bookId") Long bookId) { - return bookService.getBookSubscribersNumber(bookId); - } + @PostMapping("/delete/{id}") + public String delete( + @PathVariable(name = "id") Long id, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + RedirectAttributes redirectAttributes) { + redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page); + bookService.delete(id); + return Constants.REDIRECT_VIEW + URL; + } } diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/types/TypeController.java b/SpringApp/library/src/main/java/com/ip/library/controllers/types/TypeController.java index 4a85ba8..fa3e1ff 100644 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/types/TypeController.java +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/types/TypeController.java @@ -1,27 +1,27 @@ package com.ip.library.controllers.types; -import java.util.List; - import org.modelmapper.ModelMapper; -import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.ip.library.controllers.users.UserRole; import com.ip.library.core.configuration.Constants; import jakarta.validation.Valid; @RestController -@Secured(value = UserRole.Secured.ADMIN) -@RequestMapping(Constants.API_URL + "/type") +@RequestMapping(TypeController.URL) public class TypeController { + public static final String URL = Constants.API_URL + "/type"; + private static final String TYPE_VIEW = "type"; + private static final String TYPE_EDIT_VIEW = "type-edit"; + private static final String TYPE_ATTRIBUTE = "type"; + private final TypeService typeService; private final ModelMapper modelMapper; @@ -39,27 +39,66 @@ public class TypeController { } @GetMapping - public List getAll() { - return typeService.getAll().stream().map(this::toDto).toList(); + public String getAll(Model model) { + model.addAttribute( + "items", + typeService + .getAll() + .stream() + .map(this::toDto) + .toList()); + return TYPE_VIEW; } - @GetMapping("/{id}") - public TypeDto get(@PathVariable(name = "id") Long id) { - return toDto(typeService.get(id)); + @GetMapping("/edit/") + public String create(Model model) { + model.addAttribute(TYPE_ATTRIBUTE, new TypeDto()); + return TYPE_EDIT_VIEW; } - @PostMapping - public TypeDto create(@RequestBody @Valid TypeDto dto) { - return toDto(typeService.create(toEntity(dto))); + @PostMapping("/edit/") + public String create( + @ModelAttribute(name = TYPE_ATTRIBUTE) @Valid TypeDto type, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return TYPE_EDIT_VIEW; + } + typeService.create(toEntity(type)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public TypeDto update(@PathVariable(name = "id") Long id, @RequestBody TypeDto dto) { - return toDto(typeService.update(id, toEntity(dto))); + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(TYPE_ATTRIBUTE, toDto(typeService.get(id))); + return TYPE_EDIT_VIEW; } - @DeleteMapping("/{id}") - public TypeDto delete(@PathVariable(name = "id") Long id) { - return toDto(typeService.delete(id)); + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @ModelAttribute(name = TYPE_ATTRIBUTE) @Valid TypeDto type, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return TYPE_EDIT_VIEW; + } + if (id <= 0) { + throw new IllegalArgumentException(); + } + typeService.update(id, toEntity(type)); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/delete/{id}") + public String delete( + @PathVariable(name = "id") Long id) { + typeService.delete(id); + return Constants.REDIRECT_VIEW + URL; } } diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/users/LoginController.java b/SpringApp/library/src/main/java/com/ip/library/controllers/users/LoginController.java deleted file mode 100644 index 5d0acb8..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/users/LoginController.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.ip.library.controllers.users; - -import java.util.Objects; - -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import com.ip.library.core.configuration.Constants; - -@RestController -public class LoginController { - private final UserService userService; - - public LoginController(UserService userService) { - this.userService = userService; - } - - @PostMapping(Constants.LOGIN_URL) - public String login( - @RequestParam(name = "login") String login, - @RequestParam(name = "password") String password) { - return userService.login(login, password); - } - - @PostMapping(Constants.SIGNUP_URL) - public boolean signup( - @RequestParam(name = "login") String login, - @RequestParam(name = "password") String password, - @RequestParam(name = "password2") String password2) { - if (!StringUtils.hasText(login)) { - throw new IllegalArgumentException("Invalid login"); - } - if (!StringUtils.hasText(password)) { - throw new IllegalArgumentException("Invalid password"); - } - if (!Objects.equals(password, password2)) { - throw new IllegalArgumentException("Passwords are not equals"); - } - final UserEntity user = new UserEntity(login, password); - userService.create(user); - return true; - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserBookController.java b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserBookController.java deleted file mode 100644 index c68135f..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserBookController.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.ip.library.controllers.users; - -import java.util.List; - -import org.modelmapper.ModelMapper; -import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import com.ip.library.controllers.books.BookDto; -import com.ip.library.controllers.books.BookEntity; -import com.ip.library.core.configuration.Constants; - -@RestController -@Secured(value = { UserRole.Secured.USER, UserRole.Secured.ADMIN }) -@RequestMapping(Constants.API_URL + "/user/{userId}/book") -public class UserBookController { - private final UserService userService; - private final ModelMapper modelMapper; - - public UserBookController( - UserService userService, - ModelMapper modelMapper) { - this.userService = userService; - this.modelMapper = modelMapper; - } - - private BookDto toBookDto (BookEntity entity) { - BookDto bookDto = modelMapper.map(entity, BookDto.class); - bookDto.setAuthorId(entity.getAuthorsBooks().stream().map(x -> x.getAuthor().getId()).toList()); - return bookDto; - } - - @GetMapping("/{bookId}") - public boolean addFavorite( - @PathVariable(name = "userId") Long userId, - @PathVariable(name = "bookId") Long bookId) { - return userService.addFavorite(userId, bookId); - } - - @GetMapping("/all-books") - public List getUserFavorites( - @PathVariable(name = "userId") Long userId, - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { - return userService.getUserFavorities(userId, page, size).stream().map(this::toBookDto).toList(); - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserController.java b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserController.java index e8a0b1f..751b1a4 100644 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserController.java +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserController.java @@ -1,27 +1,33 @@ package com.ip.library.controllers.users; +import java.util.Map; + import org.modelmapper.ModelMapper; -import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import com.ip.library.core.api.PageDto; -import com.ip.library.core.api.PageDtoMapper; +import com.ip.library.core.api.PageAttributesMapper; import com.ip.library.core.configuration.Constants; import jakarta.validation.Valid; @RestController -@Secured(value = UserRole.Secured.ADMIN) -@RequestMapping(Constants.API_URL + "/user") +@RequestMapping(UserController.URL) public class UserController { + public static final String URL = Constants.API_URL + "/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; private final ModelMapper modelMapper; @@ -41,34 +47,81 @@ public class UserController { } @GetMapping - public PageDto getAll( - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { - return PageDtoMapper.toDto(userService.getAll(page, size), this::toUserDto); + public String getAll( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + final Map attributes = PageAttributesMapper.toAttributes( + userService.getAll(page, Constants.DEFUALT_PAGE_SIZE), this::toUserDto); + model.addAllAttributes(attributes); + model.addAttribute(PAGE_ATTRIBUTE, page); + return USER_VIEW; } - @GetMapping("/{id}") - public UserDto get(@PathVariable(name = "id") Long id) { - return toUserDto(userService.get(id)); + @GetMapping("/edit/") + 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 - public UserDto create(@RequestBody @Valid UserDto dto) { - return toUserDto(userService.create(toUserEntity(dto))); + @PostMapping("/edit/") + public String create( + @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; + } + redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page); + userService.create(toUserEntity(user)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public UserDto update(@PathVariable(name = "id") Long id, @RequestBody UserDto dto) { - return toUserDto(userService.update(id, toUserEntity(dto))); + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(USER_ATTRIBUTE, toUserDto(userService.get(id))); + model.addAttribute(PAGE_ATTRIBUTE, page); + return USER_EDIT_VIEW; } - @DeleteMapping("/{id}") - public UserDto delete(@PathVariable(name = "id") Long id) { - return toUserDto(userService.delete(id)); + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long 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, toUserEntity(user)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/password/{id}") - public UserDto changePassword(@PathVariable(name = "id") Long id, @RequestBody String newPassword) { - return toUserDto(userService.changePassword(id, newPassword)); + @PostMapping("/delete/{id}") + public String delete( + @PathVariable(name = "id") Long id, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + RedirectAttributes redirectAttributes) { + redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page); + userService.delete(id); + return Constants.REDIRECT_VIEW + URL; } } diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserEntity.java b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserEntity.java index 20195ff..29c8869 100644 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserEntity.java +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserEntity.java @@ -20,7 +20,7 @@ import jakarta.persistence.Table; public class UserEntity extends BaseEntity { @Column(nullable = false, unique = true, length = 20) private String login; - @Column(nullable = false, unique = false) + @Column(nullable = false, length = 60, unique = false) private String password; private UserRole role = UserRole.USER; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserService.java b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserService.java index ef2473d..d1d8808 100644 --- a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserService.java +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserService.java @@ -18,8 +18,6 @@ import com.ip.library.controllers.books.BookEntity; import com.ip.library.controllers.books.BookService; import com.ip.library.core.configuration.Constants; import com.ip.library.core.error.NotFoundException; -import com.ip.library.core.jwt.JwtException; -import com.ip.library.core.jwt.JwtProvider; import com.ip.library.core.security.UserPrincipal; @Service @@ -27,16 +25,13 @@ public class UserService implements UserDetailsService{ private final UserRepository repository; private final BookService bookService; private final PasswordEncoder passwordEncoder; - private final JwtProvider jwtProvider; public UserService( UserRepository repository, BookService bookService, - PasswordEncoder passwordEncoder, - JwtProvider jwtProvider) { + PasswordEncoder passwordEncoder) { this.repository = repository; this.bookService = bookService; - this.jwtProvider = jwtProvider; this.passwordEncoder = passwordEncoder; } @@ -138,24 +133,6 @@ public class UserService implements UserDetailsService{ return repository.getUserFavorities(userId, PageRequest.of(page, size)); } - @Transactional(readOnly = true) - public UserDetails getByToken(String token) { - if (!jwtProvider.isTokenValid(token)) - throw new JwtException("Bad token"); - final String userLogin = jwtProvider.getLoginFromToken(token) - .orElseThrow(() -> new JwtException("Token is not contain Login")); - return loadUserByUsername(userLogin); - } - - @Transactional(readOnly = true) - public String login(String login, String password) { - final UserEntity existsUser = getByLogin(login); - if (!passwordEncoder.matches(password, existsUser.getPassword())) - throw new IllegalArgumentException("Invalid login"); - return jwtProvider.generateToken(existsUser.getLogin()); - - } - @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserSignupController.java b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserSignupController.java new file mode 100644 index 0000000..467a28d --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserSignupController.java @@ -0,0 +1,63 @@ +package com.ip.library.controllers.users; + +import java.util.Objects; + +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.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.ip.library.core.configuration.Constants; + +import jakarta.validation.Valid; + +@Controller +@RequestMapping(UserSignupController.URL) +public class UserSignupController { + public static final String URL = Constants.SIGNUP_URL; + + private static final String SIGNUP_VIEW = "signup"; + private static final String USER_ATTRIBUTE = "user"; + + private final UserService userService; + private final ModelMapper modelMapper; + + public UserSignupController( + UserService userService, + ModelMapper modelMapper) { + this.userService = userService; + this.modelMapper = modelMapper; + } + + private UserEntity toEntity(UserSignupDto dto) { + return modelMapper.map(dto, UserEntity.class); + } + + @GetMapping + public String getSignup(Model model) { + model.addAttribute(USER_ATTRIBUTE, new UserSignupDto()); + return SIGNUP_VIEW; + } + + @PostMapping + public String signup( + @ModelAttribute(name = USER_ATTRIBUTE) @Valid UserSignupDto user, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return SIGNUP_VIEW; + } + if (!Objects.equals(user.getPassword(), user.getPasswordConfirm())) { + bindingResult.rejectValue("password", "signup:passwords", "Пароли не совпадают."); + model.addAttribute(USER_ATTRIBUTE, user); + return SIGNUP_VIEW; + } + userService.create(toEntity(user)); + return Constants.REDIRECT_VIEW + Constants.LOGIN_URL + "?signup"; + } + +} diff --git a/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserSignupDto.java b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserSignupDto.java new file mode 100644 index 0000000..5189fd1 --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/controllers/users/UserSignupDto.java @@ -0,0 +1,40 @@ +package com.ip.library.controllers.users; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class UserSignupDto { + @NotBlank + @Size(min = 3, max = 20) + private String login; + @NotBlank + @Size(min = 3, max = 60) + private String password; + @NotBlank + @Size(min = 3, max = 60) + private String passwordConfirm; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + 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; + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/api/PageAttributesMapper.java b/SpringApp/library/src/main/java/com/ip/library/core/api/PageAttributesMapper.java new file mode 100644 index 0000000..68aa00a --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/core/api/PageAttributesMapper.java @@ -0,0 +1,18 @@ +package com.ip.library.core.api; + +import java.util.Map; +import java.util.function.Function; + +import org.springframework.data.domain.Page; + +public class PageAttributesMapper { + private PageAttributesMapper() { + } + + public static Map toAttributes(Page page, Function mapper) { + return Map.of( + "items", page.getContent().stream().map(mapper::apply).toList(), + "currentPage", page.getNumber(), + "totalPages", page.getTotalPages()); + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/api/PageDto.java b/SpringApp/library/src/main/java/com/ip/library/core/api/PageDto.java deleted file mode 100644 index 1de2fe4..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/api/PageDto.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.ip.library.core.api; - -import java.util.ArrayList; -import java.util.List; - -public class PageDto { - private List items = new ArrayList<>(); - private int itemsCount; - private int currentPage; - private int currentSize; - private int totalPages; - private long totalItems; - private boolean isFirst; - private boolean isLast; - private boolean hasNext; - private boolean hasPrevious; - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public int getItemsCount() { - return itemsCount; - } - - public void setItemsCount(int itemsCount) { - this.itemsCount = itemsCount; - } - - public int getCurrentPage() { - return currentPage; - } - - public void setCurrentPage(int currentPage) { - this.currentPage = currentPage; - } - - public int getCurrentSize() { - return currentSize; - } - - public void setCurrentSize(int currentSize) { - this.currentSize = currentSize; - } - - public int getTotalPages() { - return totalPages; - } - - public void setTotalPages(int totalPages) { - this.totalPages = totalPages; - } - - public long getTotalItems() { - return totalItems; - } - - public void setTotalItems(long totalItems) { - this.totalItems = totalItems; - } - - public boolean isFirst() { - return isFirst; - } - - public void setFirst(boolean isFirst) { - this.isFirst = isFirst; - } - - public boolean isLast() { - return isLast; - } - - public void setLast(boolean isLast) { - this.isLast = isLast; - } - - public boolean isHasNext() { - return hasNext; - } - - public void setHasNext(boolean hasNext) { - this.hasNext = hasNext; - } - - public boolean isHasPrevious() { - return hasPrevious; - } - - public void setHasPrevious(boolean hasPrevious) { - this.hasPrevious = hasPrevious; - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/api/PageDtoMapper.java b/SpringApp/library/src/main/java/com/ip/library/core/api/PageDtoMapper.java deleted file mode 100644 index 71ce537..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/api/PageDtoMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ip.library.core.api; - -import java.util.function.Function; - -import org.springframework.data.domain.Page; - -public class PageDtoMapper { - private PageDtoMapper() {} - - public static PageDto toDto(Page page, Function mapper) { - final PageDto dto = new PageDto<>(); - dto.setItems(page.getContent().stream().map(mapper::apply).toList()); - dto.setItemsCount(page.getNumberOfElements()); - dto.setCurrentPage(page.getNumber()); - dto.setCurrentSize(page.getSize()); - dto.setTotalPages(page.getTotalPages()); - dto.setTotalItems(page.getTotalElements()); - dto.setFirst(page.isFirst()); - dto.setLast(page.isLast()); - dto.setHasNext(page.hasNext()); - dto.setHasPrevious(page.hasPrevious()); - return dto; - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java b/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java index 9d59937..0069011 100644 --- a/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java +++ b/SpringApp/library/src/main/java/com/ip/library/core/configuration/Constants.java @@ -3,11 +3,14 @@ package com.ip.library.core.configuration; public class Constants { public static final String SEQUENCE_NAME = "hibernate_sequence"; + public static final int DEFUALT_PAGE_SIZE = 5; + public static final String API_URL = "/api/1.0"; - public static final String DEFAULT_PAGE_SIZE = "5"; + public static final String REDIRECT_VIEW = "redirect:"; public static final String LOGIN_URL = "/login"; + public static final String LOGOUT_URL = "/logout"; public static final String SIGNUP_URL = "/signup"; public static final String DEFAULT_PASSWORD = "123456"; diff --git a/SpringApp/library/src/main/java/com/ip/library/core/configuration/MapperConfiguration.java b/SpringApp/library/src/main/java/com/ip/library/core/configuration/MapperConfiguration.java index f7c2cf7..f5cc8b4 100644 --- a/SpringApp/library/src/main/java/com/ip/library/core/configuration/MapperConfiguration.java +++ b/SpringApp/library/src/main/java/com/ip/library/core/configuration/MapperConfiguration.java @@ -1,13 +1,23 @@ package com.ip.library.core.configuration; import org.modelmapper.ModelMapper; +import org.modelmapper.PropertyMap; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.ip.library.core.model.BaseEntity; + @Configuration public class MapperConfiguration { @Bean ModelMapper modelMapper() { - return new ModelMapper(); + final ModelMapper mapper = new ModelMapper(); + mapper.addMappings(new PropertyMap() { + @Override + protected void configure() { + skip(destination.getId()); + } + }); + return mapper; } } diff --git a/SpringApp/library/src/main/java/com/ip/library/core/configuration/WebConfiguration.java b/SpringApp/library/src/main/java/com/ip/library/core/configuration/WebConfiguration.java new file mode 100644 index 0000000..096ad8a --- /dev/null +++ b/SpringApp/library/src/main/java/com/ip/library/core/configuration/WebConfiguration.java @@ -0,0 +1,14 @@ +package com.ip.library.core.configuration; + +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfiguration implements WebMvcConfigurer { + @Override + public void addViewControllers(@NonNull ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); + } +} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/error/AdviceController.java b/SpringApp/library/src/main/java/com/ip/library/core/error/AdviceController.java index 27ead98..5388f1d 100644 --- a/SpringApp/library/src/main/java/com/ip/library/core/error/AdviceController.java +++ b/SpringApp/library/src/main/java/com/ip/library/core/error/AdviceController.java @@ -1,83 +1,53 @@ package com.ip.library.core.error; -import java.io.IOException; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.ModelAndView; -import com.fasterxml.jackson.databind.ObjectMapper; - -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -@RestControllerAdvice -public class AdviceController implements AuthenticationEntryPoint { +@ControllerAdvice +public class AdviceController { private final Logger log = LoggerFactory.getLogger(AdviceController.class); - public static ErrorCauseDto getRootCause(Throwable throwable) { + private static Throwable getRootCause(Throwable throwable) { Throwable rootCause = throwable; while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { rootCause = rootCause.getCause(); } - final StackTraceElement firstError = rootCause.getStackTrace()[0]; - return new ErrorCauseDto( - rootCause.getClass().getName(), - firstError.getMethodName(), - firstError.getFileName(), - firstError.getLineNumber()); + return rootCause; } - private ResponseEntity handleException(Throwable throwable, HttpStatusCode httpCode) { + private static Map getAttributes(HttpServletRequest request, Throwable throwable) { + final Throwable rootCause = getRootCause(throwable); + final StackTraceElement firstError = rootCause.getStackTrace()[0]; + return Map.of( + "message", rootCause.getMessage(), + "url", request.getRequestURL(), + "exception", rootCause.getClass().getName(), + "file", firstError.getFileName(), + "method", firstError.getMethodName(), + "line", firstError.getLineNumber()); + } + + @ExceptionHandler(value = Exception.class) + public ModelAndView defaultErrorHandler(HttpServletRequest request, Throwable throwable) throws Throwable { + if (AnnotationUtils.findAnnotation(throwable.getClass(), + ResponseStatus.class) != null) { + throw throwable; + } + log.error("{}", throwable.getMessage()); throwable.printStackTrace(); - final ErrorDto errorDto = new ErrorDto(throwable.getMessage(), AdviceController.getRootCause(throwable)); - return new ResponseEntity<>(errorDto, httpCode); - } - - @Override - public void commence( - HttpServletRequest request, - HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { - final ResponseEntity body = handleException(authException, HttpStatus.UNAUTHORIZED); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setStatus(body.getStatusCode().value()); - response.getWriter().write(new ObjectMapper().writeValueAsString(body.getBody())); - } - - @ExceptionHandler(AccessDeniedException.class) - @ResponseStatus(HttpStatus.FORBIDDEN) - public ResponseEntity handleAccessDeniedException(Throwable throwable) { - return handleException(throwable, HttpStatus.FORBIDDEN); - } - - @ExceptionHandler(NotFoundException.class) - @ResponseStatus(HttpStatus.NOT_FOUND) - public ResponseEntity handleNotFoundException(Throwable throwable) { - return handleException(throwable, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ResponseEntity handleDataIntegrityViolationException(Throwable throwable) { - return handleException(throwable, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ResponseEntity handleAnyException(Throwable throwable) { - return handleException(throwable, HttpStatus.INTERNAL_SERVER_ERROR); + final ModelAndView model = new ModelAndView(); + model.addAllObjects(getAttributes(request, throwable)); + model.setViewName("error"); + return model; } } diff --git a/SpringApp/library/src/main/java/com/ip/library/core/error/ErrorCauseDto.java b/SpringApp/library/src/main/java/com/ip/library/core/error/ErrorCauseDto.java deleted file mode 100644 index 7feee4d..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/error/ErrorCauseDto.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.ip.library.core.error; - -class ErrorCauseDto { - private String exception; - private String methodName; - private String fineName; - private int lineNumber; - - ErrorCauseDto(String exception, String methodName, String fineName, int lineNumber) { - this.exception = exception; - this.methodName = methodName; - this.fineName = fineName; - this.lineNumber = lineNumber; - } - - public String getException() { - return exception; - } - - public String getMethodName() { - return methodName; - } - - public String getFineName() { - return fineName; - } - - public int getLineNumber() { - return lineNumber; - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/error/ErrorDto.java b/SpringApp/library/src/main/java/com/ip/library/core/error/ErrorDto.java deleted file mode 100644 index c3eed48..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/error/ErrorDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ip.library.core.error; - -public class ErrorDto { - private String error; - private ErrorCauseDto rootCause; - - public ErrorDto(String error, ErrorCauseDto rootCause) { - this.error = error; - this.rootCause = rootCause; - } - - public String getError() { - return error; - } - - public ErrorCauseDto getRootCause() { - return rootCause; - } - -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtException.java b/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtException.java deleted file mode 100644 index 3cff4f5..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ip.library.core.jwt; - -public class JwtException extends RuntimeException { - public JwtException(Throwable throwable) { - super(throwable); - } - - public JwtException(String message) { - super(message); - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtFilter.java b/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtFilter.java deleted file mode 100644 index 49f5bb7..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.ip.library.core.jwt; - -import java.io.IOException; - -import org.springframework.lang.NonNull; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; - -import com.ip.library.controllers.users.UserService; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public class JwtFilter extends OncePerRequestFilter { - private static final String AUTHORIZATION = "Authorization"; - public static final String TOKEN_BEGIN_STR = "Bearer "; - - private final UserService userService; - - public JwtFilter(UserService userService) { - this.userService = userService; - } - - private String getTokenFromRequest(HttpServletRequest request) { - String bearer = request.getHeader(AUTHORIZATION); - if (StringUtils.hasText(bearer) && StringUtils.startsWithIgnoreCase(bearer, TOKEN_BEGIN_STR)) { - return bearer.substring(TOKEN_BEGIN_STR.length()); - } - return null; - } - - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain) throws ServletException, IOException { - final String token = getTokenFromRequest(request); - if (!StringUtils.hasText(token)) { - filterChain.doFilter(request, response); - return; - } - final UserDetails user = userService.getByToken(token); - final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( - user, - null, - user.getAuthorities()); - final SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(auth); - SecurityContextHolder.setContext(context); - filterChain.doFilter(request, response); - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtProperties.java b/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtProperties.java deleted file mode 100644 index 3df306e..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtProperties.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ip.library.core.jwt; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true) -public class JwtProperties { - private String key = ""; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtProvider.java b/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtProvider.java deleted file mode 100644 index 1083a4d..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/jwt/JwtProvider.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.ip.library.core.jwt; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.Date; -import java.util.Optional; -import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.exceptions.JWTVerificationException; -import com.auth0.jwt.interfaces.DecodedJWT; -import com.auth0.jwt.interfaces.JWTVerifier; - -@Component -public class JwtProvider { - private static final Logger log = LoggerFactory.getLogger(JwtProvider.class); - - private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); - private static final String ISSUER = "auth0"; - - private final Algorithm algorithm; - private final JWTVerifier verifier; - - public JwtProvider(JwtProperties jwtProperties) { - final String key = jwtProperties.getKey(); - if (!StringUtils.hasText(key)) { - log.info("Generate new JWT key"); - try { - final MessageDigest salt = MessageDigest.getInstance("SHA-256"); - salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8)); - final String hex = bytesToHex(salt.digest()); - log.info("Use generated JWT key for prod \n{}", hex); - algorithm = Algorithm.HMAC256(hex); - } catch (NoSuchAlgorithmException e) { - throw new JwtException(e); - } - } else { - log.info("Use JWT key from config \n{}", key); - algorithm = Algorithm.HMAC256(key); - } - verifier = JWT.require(algorithm) - .withIssuer(ISSUER) - .build(); - } - - private static String bytesToHex(byte[] bytes) { - byte[] hexChars = new byte[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - return new String(hexChars, StandardCharsets.UTF_8); - } - - public String generateToken(String login) { - final Date issueDate = Date.from(LocalDate.now() - .atStartOfDay(ZoneId.systemDefault()) - .toInstant()); - final Date expireDate = Date.from(LocalDate.now() - .plusDays(15) - .atStartOfDay(ZoneId.systemDefault()) - .toInstant()); - return JWT.create() - .withIssuer(ISSUER) - .withIssuedAt(issueDate) - .withExpiresAt(expireDate) - .withSubject(login) - .sign(algorithm); - } - - private DecodedJWT validateToken(String token) { - try { - return verifier.verify(token); - } catch (JWTVerificationException e) { - throw new JwtException(String.format("Token verification error: %s", e.getMessage())); - } - } - - public boolean isTokenValid(String token) { - if (!StringUtils.hasText(token)) { - return false; - } - try { - validateToken(token); - return true; - } catch (JwtException e) { - log.error(e.getMessage()); - return false; - } - } - - public Optional getLoginFromToken(String token) { - try { - return Optional.ofNullable(validateToken(token).getSubject()); - } catch (JwtException e) { - log.error(e.getMessage()); - return Optional.empty(); - } - } -} diff --git a/SpringApp/library/src/main/java/com/ip/library/core/security/SecurityConfiguration.java b/SpringApp/library/src/main/java/com/ip/library/core/security/SecurityConfiguration.java index c20ff78..776923c 100644 --- a/SpringApp/library/src/main/java/com/ip/library/core/security/SecurityConfiguration.java +++ b/SpringApp/library/src/main/java/com/ip/library/core/security/SecurityConfiguration.java @@ -2,60 +2,55 @@ package com.ip.library.core.security; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.Customizer; -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.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.util.CollectionUtils; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; +import com.ip.library.controllers.authors.AuthorController; +import com.ip.library.controllers.books.BookController; +import com.ip.library.controllers.types.TypeController; +import com.ip.library.controllers.users.UserController; import com.ip.library.controllers.users.UserRole; -import com.ip.library.controllers.users.UserService; +import com.ip.library.controllers.users.UserSignupController; import com.ip.library.core.configuration.Constants; -import com.ip.library.core.error.AdviceController; -import com.ip.library.core.jwt.JwtFilter; @Configuration @EnableWebSecurity -@EnableMethodSecurity(securedEnabled = true) public class SecurityConfiguration { @Bean - SecurityFilterChain filterChain( - HttpSecurity httpSecurity, - UserService userService, - AdviceController adviceController) throws Exception { + SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin)); httpSecurity.csrf(AbstractHttpConfigurer::disable); httpSecurity.cors(Customizer.withDefaults()); - httpSecurity.sessionManagement(sessionManagement -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); httpSecurity.authorizeHttpRequests(requests -> requests - .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html") + .requestMatchers("/css/**", "/webjars/**", "/*.svg") .permitAll()); httpSecurity.authorizeHttpRequests(requests -> requests .requestMatchers("/h2-console/**").hasRole(UserRole.ADMIN.name()) - .requestMatchers(HttpMethod.POST, Constants.SIGNUP_URL).anonymous() - .requestMatchers(HttpMethod.POST, Constants.LOGIN_URL).anonymous() + .requestMatchers(TypeController.URL).hasRole(UserRole.ADMIN.name()) + .requestMatchers(BookController.URL).hasRole(UserRole.ADMIN.name()) + .requestMatchers(UserController.URL).hasRole(UserRole.ADMIN.name()) + .requestMatchers(AuthorController.URL).hasRole(UserRole.ADMIN.name()) + .requestMatchers(UserSignupController.URL).anonymous() + .requestMatchers(Constants.LOGIN_URL).anonymous() .anyRequest().authenticated()); - httpSecurity.exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(adviceController)); + httpSecurity.formLogin(formLogin -> formLogin + .loginPage(Constants.LOGIN_URL)); - httpSecurity.addFilterBefore(new JwtFilter(userService), UsernamePasswordAuthenticationFilter.class); + httpSecurity.rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret")); + + httpSecurity.logout(logout -> logout + .deleteCookies("JSESSIONID")); return httpSecurity.build(); } @@ -72,18 +67,4 @@ public class SecurityConfiguration { PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - - @Bean - CorsFilter corsFilter() { - final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - final CorsConfiguration config = new CorsConfiguration(); - if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) - || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) { - source.registerCorsConfiguration(Constants.API_URL + "/**", config); - source.registerCorsConfiguration("/v3/api-docs", config); - source.registerCorsConfiguration("/swagger-ui/**", config); - } - return new CorsFilter(source); - } - } diff --git a/SpringApp/library/src/main/java/com/ip/library/core/security/SwaggerConfiguration.java b/SpringApp/library/src/main/java/com/ip/library/core/security/SwaggerConfiguration.java deleted file mode 100644 index 88a9b5f..0000000 --- a/SpringApp/library/src/main/java/com/ip/library/core/security/SwaggerConfiguration.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.ip.library.core.security; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; - -@Configuration -@OpenAPIDefinition -public class SwaggerConfiguration { - private static final String SCHEME_NAME = "JWT"; - private static final String SCHEME = "bearer"; - - private SecurityScheme createBearerScheme() { - return new SecurityScheme() - .type(SecurityScheme.Type.HTTP) - .scheme(SCHEME); - } - - @Bean - OpenAPI customOpenApi() { - return new OpenAPI() - .components(new Components() - .addSecuritySchemes(SCHEME_NAME, createBearerScheme())) - .addSecurityItem(new SecurityRequirement().addList(SCHEME_NAME)); - } -} diff --git a/SpringApp/library/src/main/resources/public/css/style.css b/SpringApp/library/src/main/resources/public/css/style.css new file mode 100644 index 0000000..7423490 --- /dev/null +++ b/SpringApp/library/src/main/resources/public/css/style.css @@ -0,0 +1,67 @@ +html, +body { + height: 100%; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.25em; +} + +h3 { + font-size: 1.1em; +} + +td form { + margin: 0; + padding: 0; + margin-top: -.25em; +} + +.button-fixed-width { + width: 150px; +} + +.button-link { + padding: 0; +} + +.invalid-feedback { + display: block; +} + +.w-10 { + width: 10% !important; +} + +.my-navbar { + background-color: #3c3c3c !important; +} + +.my-navbar .link a:hover { + text-decoration: underline; +} + +.my-navbar .logo { + width: 26px; + height: 26px; +} + +.my-footer { + background-color: #2c2c2c; + height: 32px; + color: rgba(255, 255, 255, 0.5); +} + +.cart-image { + width: 3.1rem; + padding: 0.25rem; + border-radius: 0.5rem; +} + +.cart-item { + height: auto; +} \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/public/favicon.svg b/SpringApp/library/src/main/resources/public/favicon.svg new file mode 100644 index 0000000..50f9e3f --- /dev/null +++ b/SpringApp/library/src/main/resources/public/favicon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/author-edit.html b/SpringApp/library/src/main/resources/templates/author-edit.html new file mode 100644 index 0000000..1ffe1c7 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/author-edit.html @@ -0,0 +1,28 @@ + + + + + Редакторовать автора + + + +
+
+
+ + +
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/author.html b/SpringApp/library/src/main/resources/templates/author.html new file mode 100644 index 0000000..c79c8d6 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/author.html @@ -0,0 +1,50 @@ + + + + + Авторы + + + +
+ +

Данные отсутствуют

+ +

Авторы

+ + + + + + + + + + + + + + + + + + + +
IDАвтор
+
+ +
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/book-edit.html b/SpringApp/library/src/main/resources/templates/book-edit.html new file mode 100644 index 0000000..fcc1992 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/book-edit.html @@ -0,0 +1,29 @@ + + + + + Редакторовать книгу + + + +
+
+
+ + +
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/book.html b/SpringApp/library/src/main/resources/templates/book.html new file mode 100644 index 0000000..06b6c17 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/book.html @@ -0,0 +1,58 @@ + + + + + Книги + + + +
+ +

Данные отсутствуют

+ +

Книги

+ + + + + + + + + + + + + + + + + + + + + +
IDНазваниеЖанр
+
+ + +
+
+
+ + +
+
+
+ + +
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/default.html b/SpringApp/library/src/main/resources/templates/default.html new file mode 100644 index 0000000..85ddee1 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/default.html @@ -0,0 +1,71 @@ + + + + + + + + My shop + + + + + + + + +
+
+
+ Автор, [[${#dates.year(#dates.createNow())}]] +
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/error.html b/SpringApp/library/src/main/resources/templates/error.html new file mode 100644 index 0000000..faa6b0a --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/error.html @@ -0,0 +1,37 @@ + + + + + Ошибка + + + +
+
    + +
  • + Неизвестная ошибка +
  • +
    + +
  • + Ошибка: [[${message}]] +
  • +
    + +
  • + Адрес: [[${url}]] +
  • +
  • + Класс исключения: [[${exception}]] +
  • +
  • + [[${method}]] ([[${file}]]:[[${line}]]) +
  • +
    +
+ На главную +
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/login.html b/SpringApp/library/src/main/resources/templates/login.html new file mode 100644 index 0000000..eb3a6d7 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/login.html @@ -0,0 +1,44 @@ + + + + + Вход + + + +
+
+
+ Неверный логин или пароль +
+
+ Выход успешно произведен +
+
+ Пользователь успешно создан +
+
+ + +
+
+ + +
+
+ + +
+
+ + Регистрация +
+
+
+ + + + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/pagination.html b/SpringApp/library/src/main/resources/templates/pagination.html new file mode 100644 index 0000000..12354aa --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/pagination.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/signup.html b/SpringApp/library/src/main/resources/templates/signup.html new file mode 100644 index 0000000..293fcaa --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/signup.html @@ -0,0 +1,37 @@ + + + + + Вход + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + Отмена +
+
+
+ + + + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/type-edit.html b/SpringApp/library/src/main/resources/templates/type-edit.html new file mode 100644 index 0000000..97635c5 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/type-edit.html @@ -0,0 +1,28 @@ + + + + + Редакторовать жанр + + + +
+
+
+ + +
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/type.html b/SpringApp/library/src/main/resources/templates/type.html new file mode 100644 index 0000000..2343c60 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/type.html @@ -0,0 +1,50 @@ + + + + + Жанры книг + + + +
+ +

Данные отсутствуют

+ +

Жанры книг

+ + + + + + + + + + + + + + + + + + + +
IDЖанр
+
+ +
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/user-edit.html b/SpringApp/library/src/main/resources/templates/user-edit.html new file mode 100644 index 0000000..b720cd0 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/user-edit.html @@ -0,0 +1,29 @@ + + + + + Редакторовать пользователя + + + +
+
+
+ + +
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/SpringApp/library/src/main/resources/templates/user.html b/SpringApp/library/src/main/resources/templates/user.html new file mode 100644 index 0000000..6a874d2 --- /dev/null +++ b/SpringApp/library/src/main/resources/templates/user.html @@ -0,0 +1,56 @@ + + + + + Пользователи + + + +
+ +

Данные отсутствуют

+ +

Пользователи

+ + + + + + + + + + + + + + + + + + + +
IDИмя пользователя
+
+ + +
+
+
+ + +
+
+
+ + +
+ + + \ No newline at end of file