├── .gitignore ├── README.md ├── lombok.config ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── zufar │ │ └── bookshelf │ │ ├── BookshelfApplication.java │ │ ├── config │ │ ├── CustomUserDetailsService.java │ │ ├── SecurityConfig.java │ │ └── SpringSecurityInitializer.java │ │ ├── controller │ │ ├── AuthorsController.java │ │ ├── BooksController.java │ │ ├── CustomErrorController.java │ │ ├── HomeController.java │ │ └── UserController.java │ │ ├── dao │ │ ├── AbstractAuditingEntity.java │ │ ├── author │ │ │ ├── AuthorRepository.java │ │ │ └── model │ │ │ │ └── Author.java │ │ ├── book │ │ │ ├── BookRepository.java │ │ │ └── model │ │ │ │ └── Book.java │ │ ├── country │ │ │ ├── CountryRepository.java │ │ │ └── model │ │ │ │ └── Country.java │ │ └── user │ │ │ ├── RoleRepository.java │ │ │ ├── UserRepository.java │ │ │ └── model │ │ │ ├── Gender.java │ │ │ ├── Role.java │ │ │ └── User.java │ │ └── service │ │ ├── LoadDatabase.java │ │ └── UserService.java └── resources │ ├── application-dev.yaml │ ├── application-prod.yaml │ ├── application-test.yaml │ ├── application.yaml │ ├── static │ ├── banner.txt │ └── css │ │ ├── admin.css │ │ ├── adminBooklist.css │ │ ├── bookProfile.css │ │ ├── booklist.css │ │ ├── index.css │ │ ├── mainStyle.css │ │ ├── profile.css │ │ ├── registration.css │ │ ├── review.css │ │ └── userProfile.css │ └── templates │ ├── crud │ ├── addAuthorView.html │ ├── addBookView.html │ ├── updateAuthorView.html │ └── updateBookView.html │ ├── error │ ├── error-404.html │ └── error-500.html │ ├── home.html │ ├── lists │ ├── authorListView.html │ └── bookListView.html │ ├── login.html │ ├── profiles │ ├── authorProfileView.html │ └── bookProfileView.html │ └── registrationView.html └── test └── java └── com └── zufar └── bookshelf └── BookshelfApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bookshelf 2 | Bookshelf is a great book portal for true book lovers. \ 3 | BookShelf is the system where you can: 4 | 1) **Register and authenticate in the system.** \ 5 | There are different roles of the system: 6 | - Guest (View all pages, view all reviews); 7 | - Authenticated user (Rate a book, write review, comment, like/dislike); 8 | - Admin (CRUD operations with all system’s entities). 9 | 2) **Look for a book by:** 10 |     - Title;\ 11 |     - Genres of his book;\ 12 |     - Tags;\ 13 |     - Various lists;\ 14 |     - Rating. 15 | 3) **Look for an author by:** 16 |     - Name;\ 17 |     - Genres of his book;\ 18 |     - Tags;\ 19 |     - Various lists;\ 20 |     - Rating. 21 | 4) **Rate a book:** \ 22 | Rating scale is from 1 to 10. 23 | 24 | 5) **Write a book review:** \ 25 |     - Like/Dislike a book review. \ 26 |     - Comment a book review; \ 27 |     - Like/Dislike a book review comment. 28 | 29 | 6) **Learn an info about:** \ 30 |     - A book; \ 31 |     - An author. 32 | 33 | 7) **Get a random book by randomizer:** \ 34 | Select a button and get a random book. 35 | 36 | 37 | ## Getting Started 38 | 39 | // TODO 40 | 41 | ## Deployment 42 | 43 | // TODO 44 | 45 | ## Built With 46 | 47 | * [Maven](https://maven.apache.org/) - Dependency Management 48 | 49 | ## Authors 50 | 51 | * **Zufar Sunagatov** 52 | 53 | ## Acknowledgments 54 | 55 |     I was inspired by [Kinopoisk](https://www.kinopoisk.ru) project. This is a large Russian portal dedicated to films. I decided to create my own analogue of such a portal, well, only for books. \ 56 |     I know that similar projects already exist, well, I want to create my own creation. 57 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.tostring.callsuper = CALL 2 | lombok.equalsandhashcode.callsuper = CALL 3 | lombok.accessors.chain = true -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | Zufar Sunagatov 8 | zufar.sunagatov@gmail.com 9 | https://www.linkedin.com/in/zufar-sunagatov-b35735176/ 10 | 11 | architect 12 | analyst 13 | ui designer 14 | developer 15 | tester 16 | 17 | Moscow (UTC+3:00) 18 | 19 | 20 | 21 | 4.0.0 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-parent 26 | 2.3.0.RELEASE 27 | 28 | 29 | 30 | com.zufar 31 | bookshelf 32 | 0.0.1-SNAPSHOT 33 | bookshelf 34 | 35 | 36 | 11 37 | UTF-8 38 | UTF-8 39 | 40 | 41 | 42 | 43 | 44 | org.projectlombok 45 | lombok 46 | true 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-devtools 53 | runtime 54 | true 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-actuator 59 | 60 | 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-web 66 | 67 | 68 | org.springframework.session 69 | spring-session-core 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-thymeleaf 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-hateoas 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-data-jpa 84 | 85 | 86 | org.hsqldb 87 | hsqldb 88 | runtime 89 | 90 | 91 | org.postgresql 92 | postgresql 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-cache 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-starter-security 107 | 108 | 109 | org.thymeleaf.extras 110 | thymeleaf-extras-springsecurity5 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.springframework.boot 120 | spring-boot-starter-test 121 | test 122 | 123 | 124 | org.junit.vintage 125 | junit-vintage-engine 126 | 127 | 128 | 129 | 130 | org.springframework.security 131 | spring-security-test 132 | test 133 | 134 | 135 | 136 | 137 | 138 | 139 | org.springframework.boot 140 | spring-boot-maven-plugin 141 | 142 | false 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/BookshelfApplication.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 9 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 10 | 11 | @EnableJpaAuditing 12 | @ConfigurationPropertiesScan 13 | @EnableConfigurationProperties 14 | @SpringBootApplication 15 | public class BookshelfApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(BookshelfApplication.class, args); 19 | } 20 | 21 | @Bean 22 | public BCryptPasswordEncoder getEncoder() { 23 | return new BCryptPasswordEncoder(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/config/CustomUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.config; 2 | 3 | import com.zufar.bookshelf.dao.user.model.Role; 4 | import com.zufar.bookshelf.dao.user.model.User; 5 | import com.zufar.bookshelf.service.UserService; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.Collection; 18 | 19 | @Service("customUserDetailsService") 20 | @RequiredArgsConstructor 21 | public class CustomUserDetailsService implements UserDetailsService { 22 | 23 | private final BCryptPasswordEncoder passwordEncoder; 24 | private final UserService userService; 25 | 26 | @Override 27 | @Transactional(readOnly = true) 28 | public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException { 29 | final User user; 30 | try { 31 | user = userService.getByLogin(login); 32 | } catch (Exception exc) { 33 | final String errorMessage = "Loading user details is impossible."; 34 | throw new UsernameNotFoundException(errorMessage, exc); 35 | } 36 | return new CustomUserDetails( 37 | user.getRoles(), 38 | passwordEncoder.encode(user.getPassword()), 39 | user.getFullName(), 40 | true, 41 | true, 42 | true, 43 | true 44 | ); 45 | } 46 | 47 | @Data 48 | @AllArgsConstructor 49 | @NoArgsConstructor 50 | public static class CustomUserDetails implements UserDetails { 51 | 52 | private Collection authorities; 53 | private String password; 54 | private String username; 55 | private boolean accountNonExpired; 56 | private boolean accountNonLocked; 57 | private boolean credentialsNonExpired; 58 | private boolean enabled; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 14 | 15 | @Configuration 16 | @EnableWebSecurity 17 | @RequiredArgsConstructor 18 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 19 | 20 | private final BCryptPasswordEncoder passwordEncoder; 21 | @Qualifier("customUserDetailsService") 22 | private final UserDetailsService userDetailsService; 23 | 24 | @Autowired 25 | public void configureUserDetails(AuthenticationManagerBuilder auth) throws Exception { 26 | auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 27 | } 28 | 29 | @Override 30 | protected void configure(HttpSecurity http) throws Exception { 31 | http 32 | .authorizeRequests() 33 | .antMatchers("/css/**", "/", "/home", "/addUser", "/registration", "/authors", "/books", "/authors/*", "/books/*").permitAll() 34 | .anyRequest().authenticated() 35 | .and() 36 | .formLogin() 37 | .loginPage("/login").usernameParameter("login").passwordParameter("password").defaultSuccessUrl("/home").permitAll() 38 | .and() 39 | .logout().permitAll().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/home") 40 | .and() 41 | .csrf().disable(); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/config/SpringSecurityInitializer.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.config; 2 | 3 | import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; 4 | 5 | public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/controller/AuthorsController.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.controller; 2 | 3 | import com.zufar.bookshelf.dao.author.AuthorRepository; 4 | import com.zufar.bookshelf.dao.author.model.Author; 5 | import com.zufar.bookshelf.dao.book.BookRepository; 6 | import com.zufar.bookshelf.dao.country.CountryRepository; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.ModelMap; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | 14 | @Controller("authors") 15 | @RequiredArgsConstructor 16 | public class AuthorsController { 17 | 18 | private final AuthorRepository authorRepository; 19 | 20 | private final BookRepository bookRepository; 21 | 22 | private final CountryRepository countryRepository; 23 | 24 | @GetMapping("/authors") 25 | public String getAll(ModelMap modelMap) { 26 | modelMap.addAttribute("authors", authorRepository.findAll()); 27 | return "lists/authorListView"; 28 | } 29 | 30 | @GetMapping("/authors/{id}") 31 | public String get(@PathVariable(value = "id") long id, ModelMap modelMap) { 32 | modelMap.addAttribute("author", authorRepository.findById(id).get()); 33 | return "profiles/authorProfileView"; 34 | } 35 | 36 | @GetMapping("/addAuthorForm") 37 | public String getAddForm(ModelMap modelMap) { 38 | modelMap.addAttribute("author", new Author()); 39 | modelMap.addAttribute("books", bookRepository.findAll()); 40 | modelMap.addAttribute("countries", countryRepository.findAll()); 41 | return "crud/addAuthorView"; 42 | } 43 | 44 | @GetMapping("/updateAuthorForm/{id}") 45 | public String getUpdateAuthorView(@PathVariable(value = "id") long id, ModelMap modelMap) { 46 | modelMap.addAttribute("author", authorRepository.findById(id).get()); 47 | modelMap.addAttribute("books", bookRepository.findAll()); 48 | modelMap.addAttribute("countries", countryRepository.findAll()); 49 | return "crud/updateAuthorView"; 50 | } 51 | 52 | @PostMapping("/authors") 53 | public String add(Author author, ModelMap modelMap) { 54 | authorRepository.save(author); 55 | modelMap.addAttribute("authors", authorRepository.findAll()); 56 | return "lists/authorListView"; 57 | } 58 | 59 | @PostMapping("/authors/{id}") 60 | public String update(@PathVariable(value = "id") long id, Author author, ModelMap modelMap) { 61 | author.setId(id); 62 | authorRepository.save(author); 63 | modelMap.addAttribute("authors", authorRepository.findAll()); 64 | return "lists/authorListView"; 65 | } 66 | 67 | @PostMapping("/deleteAuthor/{id}") 68 | public String delete(@PathVariable(value = "id") long id, ModelMap modelMap) { 69 | authorRepository.deleteById(id); 70 | modelMap.addAttribute("authors", authorRepository.findAll()); 71 | return "lists/authorListView"; 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/controller/BooksController.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.controller; 2 | 3 | import com.zufar.bookshelf.dao.author.AuthorRepository; 4 | import com.zufar.bookshelf.dao.book.BookRepository; 5 | import com.zufar.bookshelf.dao.book.model.Book; 6 | import com.zufar.bookshelf.dao.country.CountryRepository; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.ModelMap; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | 14 | @Controller 15 | @RequiredArgsConstructor 16 | public class BooksController { 17 | 18 | private final AuthorRepository authorRepository; 19 | 20 | private final BookRepository bookRepository; 21 | 22 | private final CountryRepository countryRepository; 23 | 24 | @GetMapping("/books") 25 | public String getAll(ModelMap modelMap) { 26 | modelMap.addAttribute("books", bookRepository.findAll()); 27 | return "lists/bookListView"; 28 | } 29 | 30 | @GetMapping("/books/{id}") 31 | public String get(@PathVariable(value = "id") long id, ModelMap modelMap) { 32 | modelMap.addAttribute("book", bookRepository.findById(id).get()); 33 | return "profiles/bookProfileView"; 34 | } 35 | 36 | @GetMapping("/addBookForm") 37 | public String getAddForm(ModelMap modelMap) { 38 | modelMap.addAttribute("book", new Book()); 39 | modelMap.addAttribute("authors", authorRepository.findAll()); 40 | modelMap.addAttribute("countries", countryRepository.findAll()); 41 | return "crud/addBookView"; 42 | } 43 | 44 | @GetMapping("/updateBookForm/{id}") 45 | public String getUpdateBookView(@PathVariable(value = "id") long id, ModelMap modelMap) { 46 | modelMap.addAttribute("book", bookRepository.findById(id).get()); 47 | modelMap.addAttribute("authors", authorRepository.findAll()); 48 | modelMap.addAttribute("countries", countryRepository.findAll()); 49 | return "crud/updateBookView"; 50 | } 51 | 52 | @PostMapping("/books") 53 | public String add(Book book, ModelMap modelMap) { 54 | bookRepository.save(book); 55 | modelMap.addAttribute("books", bookRepository.findAll()); 56 | return "lists/bookListView"; 57 | } 58 | 59 | @PostMapping("/books/{id}") 60 | public String update(@PathVariable(value = "id") long id, Book book, ModelMap modelMap) { 61 | book.setId(id); 62 | bookRepository.save(book); 63 | modelMap.addAttribute("books", bookRepository.findAll()); 64 | return "lists/bookListView"; 65 | } 66 | 67 | @PostMapping("/deleteBook/{id}") 68 | public String delete(@PathVariable(value = "id") long id, ModelMap modelMap) { 69 | bookRepository.deleteById(id); 70 | modelMap.addAttribute("books", bookRepository.findAll()); 71 | return "lists/bookListView"; 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/controller/CustomErrorController.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.controller; 2 | 3 | import org.springframework.boot.web.servlet.error.ErrorController; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | import javax.servlet.RequestDispatcher; 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | @Controller 12 | public class CustomErrorController implements ErrorController { 13 | 14 | @RequestMapping("/error") 15 | public String handleError(HttpServletRequest request) { 16 | Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); 17 | if (status != null) { 18 | int statusCode = Integer.parseInt(status.toString()); 19 | if (statusCode == HttpStatus.NOT_FOUND.value()) { 20 | return "error/error-404"; 21 | } 22 | } 23 | return "error/error-500"; 24 | } 25 | 26 | @Override 27 | public String getErrorPath() { 28 | return "/error"; 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | @Controller 7 | public class HomeController { 8 | 9 | @GetMapping({"/", "/home"}) 10 | public String getHomePage() { 11 | return "home"; 12 | } 13 | 14 | @GetMapping("/login") 15 | public String login() { 16 | return "login"; 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.controller; 2 | 3 | import com.zufar.bookshelf.dao.country.CountryRepository; 4 | import com.zufar.bookshelf.dao.user.model.User; 5 | import com.zufar.bookshelf.service.UserService; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.ModelMap; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | 11 | @Controller 12 | @RequiredArgsConstructor 13 | public class UserController { 14 | 15 | private final UserService userService; 16 | 17 | private final CountryRepository countryRepository; 18 | 19 | @PostMapping("/registration") 20 | public String getRegistrationPage(ModelMap modelMap) { 21 | modelMap.addAttribute("user", new User()); 22 | modelMap.addAttribute("countries", countryRepository.findAll()); 23 | return "registrationView"; 24 | } 25 | 26 | @PostMapping("/addUser") 27 | public String addUser(User user) { 28 | this.userService.save(user); 29 | return "home"; 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/AbstractAuditingEntity.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.data.annotation.CreatedDate; 6 | import org.springframework.data.annotation.LastModifiedDate; 7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.EntityListeners; 11 | import javax.persistence.MappedSuperclass; 12 | import java.time.LocalDateTime; 13 | 14 | @MappedSuperclass 15 | @EntityListeners(AuditingEntityListener.class) 16 | @Getter 17 | @Setter 18 | public abstract class AbstractAuditingEntity { 19 | 20 | @CreatedDate 21 | @Column(name = "create_time", updatable = false, nullable = false) 22 | private LocalDateTime createTime; 23 | 24 | @LastModifiedDate 25 | @Column(name = "modify_time", nullable = false) 26 | private LocalDateTime modifyTime; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/author/AuthorRepository.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.author; 2 | 3 | import com.zufar.bookshelf.dao.author.model.Author; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface AuthorRepository extends JpaRepository { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/author/model/Author.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.author.model; 2 | 3 | import com.zufar.bookshelf.dao.AbstractAuditingEntity; 4 | import com.zufar.bookshelf.dao.book.model.Book; 5 | import com.zufar.bookshelf.dao.country.model.Country; 6 | import lombok.*; 7 | import org.springframework.format.annotation.DateTimeFormat; 8 | 9 | import javax.persistence.*; 10 | import java.time.LocalDate; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @Setter 15 | @Getter 16 | @Entity 17 | @Table(name = "authors") 18 | public class Author extends AbstractAuditingEntity { 19 | 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | @Column(name = "image_link", nullable = false) 25 | private String imageLink; 26 | 27 | @Column(name = "full_name", nullable = false, length = 50) 28 | private String fullName; 29 | 30 | @Column(name = "nick_name", length = 50) 31 | private String nickName; 32 | 33 | @Column(nullable = false) 34 | @DateTimeFormat(pattern = "yyyy-MM-dd") 35 | private LocalDate birthday; 36 | 37 | @Column(name = "death_day") 38 | @DateTimeFormat(pattern = "yyyy-MM-dd") 39 | private LocalDate deathDay; 40 | 41 | @OneToOne 42 | @JoinColumn(name = "country_id") 43 | private Country country; 44 | 45 | @ManyToMany(fetch = FetchType.EAGER) 46 | @JoinTable(name = "Authors_Books", 47 | joinColumns = {@JoinColumn(name = "author_id")}, 48 | inverseJoinColumns = {@JoinColumn(name = "book_id")} 49 | ) 50 | private List books; 51 | 52 | public void update(Author author) { 53 | this.id = author.getId(); 54 | this.imageLink = author.getImageLink(); 55 | this.fullName = author.getFullName(); 56 | this.nickName = author.getNickName(); 57 | this.birthday = author.getBirthday(); 58 | this.deathDay = author.getDeathDay(); 59 | this.country = author.getCountry(); 60 | this.books = author.getBooks(); 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | List bookTitles = new ArrayList<>(); 66 | if (this.books != null) { 67 | books.forEach(author -> bookTitles.add(author.getTitle())); 68 | } 69 | return "Author{" + 70 | "id=" + id + 71 | ", imageLink='" + imageLink + '\'' + 72 | ", fullName='" + fullName + '\'' + 73 | ", nickName='" + nickName + '\'' + 74 | ", birthday=" + birthday + 75 | ", deathDay=" + deathDay + 76 | ", country=" + country + 77 | ", books=" + bookTitles + 78 | '}'; 79 | } 80 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/book/BookRepository.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.book; 2 | 3 | import com.zufar.bookshelf.dao.book.model.Book; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface BookRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/book/model/Book.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.book.model; 2 | 3 | import com.zufar.bookshelf.dao.AbstractAuditingEntity; 4 | import com.zufar.bookshelf.dao.author.model.Author; 5 | import com.zufar.bookshelf.dao.country.model.Country; 6 | import lombok.*; 7 | import org.springframework.format.annotation.DateTimeFormat; 8 | 9 | import javax.persistence.*; 10 | import java.time.LocalDate; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @Setter 15 | @Getter 16 | @Entity 17 | @Table(name = "books") 18 | public class Book extends AbstractAuditingEntity { 19 | 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | @Column(nullable = false) 25 | private String title; 26 | 27 | @Column(name = "image_link") 28 | private String imageLink; 29 | 30 | @Column(name = "epub_link") 31 | private String epubLink; 32 | 33 | @Column(name = "fb2_link") 34 | private String fb2Link; 35 | 36 | @Column(name = "pdf_link") 37 | private String pdfLink; 38 | 39 | @ManyToMany(mappedBy = "books", fetch = FetchType.EAGER) 40 | private List authors; 41 | 42 | @Column(name = "publication_date") 43 | @DateTimeFormat(pattern = "yyyy-MM-dd") 44 | private LocalDate publicationDate; 45 | 46 | @OneToOne 47 | @JoinColumn(name = "country_id") 48 | private Country country; 49 | 50 | @Column(name = "page_count") 51 | private int pageCount; 52 | 53 | public void update(Book book) { 54 | this.title = book.getTitle(); 55 | this.imageLink = book.getImageLink(); 56 | this.epubLink = book.getEpubLink(); 57 | this.fb2Link = book.getFb2Link(); 58 | this.pdfLink = book.getPdfLink(); 59 | this.authors = book.getAuthors(); 60 | this.publicationDate = book.getPublicationDate(); 61 | this.country = book.getCountry(); 62 | this.pageCount = book.getPageCount(); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | List authorNames = new ArrayList<>(); 68 | if (this.authors != null) { 69 | authors.forEach(author -> authorNames.add(author.getFullName())); 70 | } 71 | return "Book{" + 72 | "id=" + id + 73 | ", title='" + title + '\'' + 74 | ", imageLink='" + imageLink + '\'' + 75 | ", epubLink='" + epubLink + '\'' + 76 | ", fb2Link='" + fb2Link + '\'' + 77 | ", pdfLink='" + pdfLink + '\'' + 78 | ", authors=" + authorNames + 79 | ", publicationDate=" + publicationDate + 80 | ", country=" + country + 81 | ", pageCount=" + pageCount + 82 | '}'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/country/CountryRepository.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.country; 2 | 3 | import com.zufar.bookshelf.dao.country.model.Country; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface CountryRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/country/model/Country.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.country.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | 9 | @Entity 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Table(name = "countries") 13 | @Data 14 | public class Country { 15 | 16 | @Id 17 | @GeneratedValue 18 | private Long id; 19 | 20 | @Column(nullable = false, length = 50) 21 | private String name; 22 | 23 | public Country(String name) { 24 | this.name = name; 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/user/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.user; 2 | 3 | import com.zufar.bookshelf.dao.user.model.Role; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface RoleRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.user; 2 | 3 | import com.zufar.bookshelf.dao.user.model.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserRepository extends JpaRepository { 7 | 8 | boolean existsByLogin(String login); 9 | 10 | User getByLoginAndPassword(String login, String password); 11 | 12 | User getByLogin(String login); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/user/model/Gender.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.user.model; 2 | 3 | public enum Gender { 4 | 5 | MALE("MALE"), 6 | FEMALE("FEMALE"); 7 | 8 | private String value; 9 | 10 | Gender(String value) { 11 | this.value = value; 12 | 13 | } 14 | public String getValue() { 15 | return value; 16 | } 17 | 18 | public void setValue(String name) { 19 | this.value = name; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/user/model/Role.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.user.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.security.core.GrantedAuthority; 7 | 8 | import javax.persistence.*; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Entity 14 | @Table(name = "roles") 15 | public class Role implements GrantedAuthority { 16 | 17 | @Id 18 | @GeneratedValue 19 | private Long id; 20 | 21 | @Column(nullable = false) 22 | private String name; 23 | 24 | public String getAuthority() { 25 | return name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/dao/user/model/User.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.dao.user.model; 2 | 3 | import com.zufar.bookshelf.dao.AbstractAuditingEntity; 4 | import com.zufar.bookshelf.dao.country.model.Country; 5 | import lombok.*; 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | 8 | import javax.persistence.*; 9 | import java.time.LocalDate; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | @Setter 14 | @Getter 15 | @Entity 16 | @Table(name = "users") 17 | public class User extends AbstractAuditingEntity { 18 | 19 | @Id 20 | @GeneratedValue 21 | private Long id; 22 | 23 | @Column(name = "full_name", nullable = false) 24 | private String fullName; 25 | 26 | @Column(name = "nick_name", nullable = false) 27 | private String nickName; 28 | 29 | @Column(nullable = false) 30 | @DateTimeFormat(pattern = "yyyy-MM-dd") 31 | private LocalDate birthday; 32 | 33 | @OneToOne(cascade = CascadeType.ALL) 34 | @JoinColumn(name = "country_id") 35 | private Country country; 36 | 37 | @Column(nullable = false, updatable = false) 38 | @Enumerated(EnumType.STRING) 39 | private Gender gender; 40 | 41 | @Column(nullable = false) 42 | private String login; 43 | 44 | @Column(nullable = false) 45 | private String password; 46 | 47 | @OneToMany 48 | private Set roles; 49 | 50 | public void addRole(Role role) { 51 | if (this.roles == null) { 52 | this.roles = new HashSet<>(); 53 | } 54 | if (this.roles.contains(role)) { 55 | return; 56 | } 57 | this.roles.add(role); 58 | } 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/service/LoadDatabase.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.service; 2 | 3 | import com.zufar.bookshelf.dao.author.AuthorRepository; 4 | import com.zufar.bookshelf.dao.author.model.Author; 5 | import com.zufar.bookshelf.dao.book.BookRepository; 6 | import com.zufar.bookshelf.dao.book.model.Book; 7 | import com.zufar.bookshelf.dao.country.CountryRepository; 8 | import com.zufar.bookshelf.dao.country.model.Country; 9 | import com.zufar.bookshelf.dao.user.RoleRepository; 10 | import com.zufar.bookshelf.dao.user.UserRepository; 11 | import com.zufar.bookshelf.dao.user.model.Gender; 12 | import com.zufar.bookshelf.dao.user.model.Role; 13 | import com.zufar.bookshelf.dao.user.model.User; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.boot.CommandLineRunner; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | import java.time.LocalDate; 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | @Slf4j 24 | @Configuration 25 | class LoadDatabase { 26 | 27 | @Bean 28 | CommandLineRunner initDatabase(AuthorRepository authorRepository, 29 | BookRepository bookRepository, 30 | CountryRepository countryRepository, 31 | RoleRepository roleRepository, 32 | UserRepository userRepository) { 33 | return args -> { 34 | 35 | Role role_admin = new Role(1L, "ROLE_ADMIN"); 36 | Role role_user = new Role(2L, "ROLE_USER"); 37 | 38 | roleRepository.save(role_admin); 39 | roleRepository.save(role_user); 40 | 41 | Country russian_federation = new Country("Russian Federation"); 42 | 43 | User user = new User() 44 | .setCountry(russian_federation) 45 | .setNickName("Admin") 46 | .setPassword("Admin") 47 | .setGender(Gender.MALE) 48 | .setLogin("Admin") 49 | .setFullName("Admin") 50 | .setRoles(Set.of(role_admin)) 51 | .setBirthday(LocalDate.of(1903, 6, 25)); 52 | 53 | user.addRole(role_admin); 54 | userRepository.save(user); 55 | 56 | countryRepository.save(russian_federation); 57 | countryRepository.save(new Country("British India")); 58 | countryRepository.save(new Country("USA")); 59 | 60 | Author orwel = authorRepository.save( 61 | new Author() 62 | .setFullName("Eric Blair Arthur") 63 | .setNickName("George Orwell") 64 | .setBirthday(LocalDate.of(1903, 6, 25)) 65 | .setDeathDay(LocalDate.of(1903, 6, 25)) 66 | .setCountry(russian_federation) 67 | .setImageLink("https://mtdata.ru/u19/photoA884/20446828934-0/original.jpg") 68 | ); 69 | 70 | Author bradbury = authorRepository.save( 71 | new Author() 72 | .setFullName("Ray Bradbury") 73 | .setNickName("авпрврвр варврварв") 74 | .setBirthday(LocalDate.of(1903, 6, 25)) 75 | .setDeathDay(LocalDate.of(1903, 6, 25)) 76 | .setCountry(russian_federation) 77 | .setImageLink("https://mtdata.ru/u19/photoA884/20446828934-0/original.jpg") 78 | ); 79 | 80 | Book book_1984 = bookRepository.save( 81 | new Book() 82 | .setTitle("Nineteen Eighty-Four") 83 | .setAuthors(List.of(bradbury)) 84 | .setEpubLink("https://avidreaders.ru/download/1984.html?f=epub") 85 | .setPageCount(456) 86 | .setCountry(russian_federation) 87 | .setPublicationDate(LocalDate.of(1903, 6, 25)) 88 | .setImageLink("https://mtdata.ru/u19/photoA884/20446828934-0/original.jpg") 89 | ); 90 | Book book_2 = bookRepository.save( 91 | new Book() 92 | .setTitle("dsgfsgsgsgsg") 93 | .setAuthors(List.of(orwel)) 94 | .setEpubLink("https://avidreaders.ru/download/1984.html?f=epub") 95 | .setPageCount(456) 96 | .setCountry(russian_federation) 97 | .setPublicationDate(LocalDate.of(1903, 6, 25)) 98 | ); 99 | 100 | }; 101 | } 102 | } -------------------------------------------------------------------------------- /src/main/java/com/zufar/bookshelf/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf.service; 2 | 3 | import com.zufar.bookshelf.dao.user.model.Role; 4 | import com.zufar.bookshelf.dao.user.model.User; 5 | import com.zufar.bookshelf.dao.user.RoleRepository; 6 | import com.zufar.bookshelf.dao.user.UserRepository; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | 15 | @Slf4j 16 | @Service 17 | @Transactional 18 | @RequiredArgsConstructor 19 | public class UserService { 20 | 21 | private final UserRepository userRepository; 22 | private final RoleRepository roleRepository; 23 | 24 | public User getByLogin(String login) { 25 | User user = userRepository.getByLogin(login); 26 | if (user != null) { 27 | Role adminRole = roleRepository.getOne(1L); 28 | user.addRole(adminRole); 29 | return user; 30 | } else { 31 | final String errorMessage = "Loading user details is impossible."; 32 | throw new UsernameNotFoundException(errorMessage); 33 | } 34 | } 35 | 36 | public User save(User user) { 37 | User savedUser = userRepository.save(user); 38 | log.info("Saving {} was successful", savedUser); 39 | return savedUser; 40 | } 41 | 42 | public void delete(User user) { 43 | userRepository.delete(user); 44 | log.info("Deleting {} was successful", user); 45 | } 46 | 47 | public User update(User user) { 48 | User savedUser = userRepository.save(user); 49 | log.info("Updating {} was successful", savedUser); 50 | return savedUser; 51 | } 52 | 53 | public User get(String login, String password) { 54 | return userRepository.getByLoginAndPassword(login, password); 55 | } 56 | 57 | public User get(Long id) { 58 | return userRepository.getOne(id); 59 | } 60 | 61 | public List getAll() { 62 | return userRepository.findAll(); 63 | } 64 | 65 | public boolean isLoginUnique(String login) { 66 | return userRepository.existsByLogin(login); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | error: 4 | whitelabel: 5 | enabled: false 6 | spring: 7 | datasource: 8 | url: 'jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1' 9 | driver-class-name: org.hsqldb.jdbc.JDBCDriver 10 | username: 'zufar' 11 | password: 'qwerty' 12 | jpa: 13 | hibernate: 14 | ddl-auto: create 15 | database: hsql 16 | thymeleaf: 17 | suffix: '.html' 18 | cache: false 19 | check-template: true 20 | check-template-location: true 21 | encoding: UTF-8 22 | banner: 23 | location: 'classpath:/static/banner.txt' 24 | devtools: 25 | restart: 26 | enabled: true 27 | livereload: 28 | enabled: true 29 | remote: 30 | secret: 'somesecret' 31 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | spring: 4 | datasource: 5 | url: 'jdbc:postgresql://localhost:5432/bookshelf' 6 | driver-class-name: org.postgresql.Driver 7 | username: 'Zufar' 8 | password: 'zufar' 9 | jpa: 10 | hibernate: 11 | ddl-auto: create 12 | database: postgresql 13 | properties: 14 | hibernate: 15 | dialect: 'org.hibernate.dialect.PostgreSQLDialect' 16 | main: 17 | banner-mode: "off" -------------------------------------------------------------------------------- /src/main/resources/application-test.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 3 | spring: 4 | datasource: 5 | url: 'jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1' 6 | driver-class-name: org.hsqldb.jdbc.JDBCDriver 7 | username: 'zufar' 8 | password: 'qwerty' 9 | jpa: 10 | hibernate: 11 | ddl-auto: create 12 | database: hsql 13 | show-sql: true 14 | banner: 15 | location: 'classpath:/static/banner.txt' 16 | data: 17 | rest: 18 | base-path: /api 19 | default-media-type: application/json 20 | detection-strategy: all -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: "test" 4 | application: 5 | name: Bookshelf -------------------------------------------------------------------------------- /src/main/resources/static/banner.txt: -------------------------------------------------------------------------------- 1 | ____ _ _ _ __ 2 | | _ \ | | | | | | / _| 3 | | |_) | ___ ___ | | __ ___ | |__ ___ | | | |_ 4 | | _ < / _ \ / _ \ | |/ / / __| | '_ \ / _ \ | | | _| 5 | | |_) | | (_) | | (_) | | < \__ \ | | | | | __/ | | | | 6 | |____/ \___/ \___/ |_|\_\ |___/ |_| |_| \___| |_| |_| 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/static/css/admin.css: -------------------------------------------------------------------------------- 1 | 2 | #search-btn { 3 | background: #f7b2d8; 4 | color: #9b4222; 5 | font: "trebuchet ms", trebuchet; 6 | padding: 10px 20px; 7 | border-radius: 0 10px 10px 0; 8 | -moz-border-radius: 0 10px 10px 0; 9 | -webkit-border-radius: 0 10px 10px 0; 10 | -o-border-radius: 0 10px 10px 0; 11 | border: 0 none; 12 | font-weight: bold; 13 | } 14 | #search-btn:hover { 15 | background: #d169e0; 16 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 17 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 18 | color: white; 19 | } 20 | 21 | #search-box { 22 | background: white; 23 | padding: 10px; 24 | border-radius: 10px 0 0 10px; 25 | -moz-border-radius: 10px 0 0 10px; 26 | -webkit-border-radius: 10px 0 0 10px; 27 | -o-border-radius: 10px 0 0 10px; 28 | border: 0 none; 29 | width: 300px; 30 | } 31 | 32 | .roup { 33 | display: flex; 34 | justify-content: space-between; 35 | flex-direction: row; 36 | } 37 | .buttonsgroup { 38 | display: flex; 39 | justify-content: flex-end; 40 | flex-direction: column; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/main/resources/static/css/adminBooklist.css: -------------------------------------------------------------------------------- 1 | 2 | /*-----------------buttons--------------------*/ 3 | input[type="text"], input[type="password"] { 4 | width: 100%; 5 | padding: 12px 20px; 6 | margin: 8px 0; 7 | display: inline-block; 8 | border: 1px solid #ccc; 9 | border-radius: 4px; 10 | box-sizing: border-box; 11 | } 12 | 13 | 14 | .registration input[type="submit"] { 15 | width: 450px; 16 | background-color: #f22e7d; 17 | color: white; 18 | padding: 14px 20px; 19 | margin: 8px 0; 20 | border: none; 21 | border-radius: 4px; 22 | cursor: pointer; 23 | } 24 | 25 | .registration input[type="submit"]:hover { 26 | background-color: #d169e0; 27 | } 28 | 29 | .registration { 30 | border-radius: 5px; 31 | padding: 20px; 32 | } 33 | 34 | /*-----------------Birthday--------------------*/ 35 | 36 | 37 | .register { 38 | display:flex; 39 | justify-content: center; 40 | } 41 | 42 | .footer { 43 | left: 0; 44 | bottom: 0; 45 | width: 100%; 46 | height: 50px; 47 | background-color: #78778f; 48 | color: white; 49 | text-align: center; 50 | } 51 | 52 | /*-------------------------Card--------------------------*/ 53 | .cards { 54 | display: flex; 55 | flex-direction: row; 56 | flex-wrap: wrap; 57 | justify-content:center; 58 | align-content: flex-start; 59 | } 60 | .card { 61 | background-color: #f2dfe9; 62 | display: flex; 63 | flex-direction: column; 64 | justify-content:space-between; 65 | width: 250px; 66 | height: 500px; 67 | margin: 10px; 68 | padding-top: 8px; 69 | padding-bottom: 5px; 70 | border: 1px solid #ccc; 71 | box-shadow: 3px 2px 6px 0px rgba(0,0,0,0.3); 72 | } 73 | .card img { 74 | margin-left: auto; 75 | margin-right: auto; 76 | margin-top: 4px; 77 | width: 220px; 78 | height: 200px; 79 | max-width: 100%; 80 | } 81 | .card .text { 82 | padding: 0 20px 20px; 83 | } 84 | .card input[type="submit"] { 85 | align-self:flex-end; 86 | margin-left:auto; 87 | margin-right:auto; 88 | background: #f22e7d; 89 | border: 1; 90 | padding-bottom: 12px; 91 | color: white; 92 | padding: 8px; 93 | width: 100%; 94 | border-radius: 7px; 95 | } 96 | 97 | .card input[type="submit"]:hover { 98 | color: white; 99 | background: #d169e0; 100 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 101 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 102 | } 103 | 104 | .super { 105 | display: flex; 106 | flex-wrap: wrap; 107 | flex-direction: row; 108 | justify-content: center; 109 | } 110 | 111 | .superEl label{ 112 | margin-left: 5px; 113 | margin-right: 10px; 114 | font-size: 19px; 115 | margin-top: 1px; 116 | color: #783852; 117 | } 118 | 119 | .super input[type="checkbox"] { 120 | background: #555; 121 | transition: all .5s ease; 122 | cursor: pointer; 123 | } 124 | 125 | #search-btn1 { 126 | background: #dd6cac; 127 | color: #ffffff; 128 | font: "trebuchet ms", trebuchet; 129 | padding: 10px 20px; 130 | border-radius: 0 10px 10px 0; 131 | -moz-border-radius: 0 10px 10px 0; 132 | -webkit-border-radius: 0 10px 10px 0; 133 | -o-border-radius: 0 10px 10px 0; 134 | border: 0 none; 135 | width: 100px; 136 | font-weight: bold; 137 | } 138 | #search-btn1:hover { 139 | background: #be4ecf; 140 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 141 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 142 | color: white; 143 | } 144 | 145 | .searchroup{ 146 | display: flex; 147 | flex-wrap: wrap; 148 | 149 | justify-content: center; 150 | margin-left: 50px; 151 | } 152 | 153 | .searchCenter { 154 | display: flex; 155 | 156 | justify-content: center; 157 | } 158 | 159 | #create { 160 | background: #dd6cac; 161 | color: #ffffff; 162 | font: "trebuchet ms", trebuchet; 163 | padding: 10px 20px; 164 | border-radius: 10px; 165 | border: 0 none; 166 | width: 200px; 167 | font-weight: bold; 168 | } 169 | 170 | #create:hover { 171 | background: #be4ecf; 172 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 173 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 174 | color: white; 175 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/bookProfile.css: -------------------------------------------------------------------------------- 1 | 2 | .Feedback-logoContainer { 3 | position: absolute; 4 | width: 100%; 5 | text-align: center; 6 | top: 20px; 7 | } 8 | 9 | .Feedback-logo { 10 | width: 100px; 11 | } 12 | 13 | .Feedback { 14 | margin: 20px auto; 15 | color: rgba(0, 0, 0, 0.75); 16 | border-radius: 8px; 17 | padding: 40px; 18 | display:block; 19 | text-align: center; 20 | max-width: 280px; 21 | } 22 | 23 | .Feedback-header { 24 | font-size: 36px; 25 | margin-bottom: 20px; 26 | font-weight: 100; 27 | } 28 | 29 | /*----------------------------------------------------------*/ 30 | 31 | .Feedback-ratingGroup { 32 | width: 100%; 33 | text-align: center; 34 | display:flex; 35 | flex-direction: row-reverse; 36 | justify-content: center; 37 | margin-bottom: 30px; 38 | } 39 | 40 | .Feedback-radio { 41 | display: none; 42 | } 43 | 44 | .Feedback-starImage { 45 | height: 50px; 46 | width: 50px; 47 | display: block; 48 | background-image: url("https://res.cloudinary.com/yourgrocer/image/upload/v1458622885/1458640633_star_p0teda.png"); 49 | background-size: 100% 100%; 50 | /*transition: 0.2s all cubic-bezier(0, 0, 0.17, 1.08);*/ 51 | } 52 | 53 | .Feedback-label { 54 | padding: 10px; 55 | font-size: 36px; 56 | color: #444; 57 | cursor: pointer; 58 | } 59 | 60 | /* Stars are yellow when checked*/ 61 | .Feedback-radio:checked ~ .Feedback-label .Feedback-starImage { 62 | background-image: url(" https://res.cloudinary.com/yourgrocer/image/upload/v1458622892/1458640634_star_hsrksa.png"); 63 | } 64 | 65 | /* Reset hover state*/ 66 | .Feedback-ratingGroup:hover .Feedback-label .Feedback-starImage { 67 | background-image: url("https://res.cloudinary.com/yourgrocer/image/upload/v1458622885/1458640633_star_p0teda.png"); 68 | } 69 | 70 | /* Stars are yellow when hover*/ 71 | .Feedback-label:hover .Feedback-starImage, 72 | .Feedback-label:hover ~ .Feedback-label .Feedback-starImage { 73 | background-image: url(" https://res.cloudinary.com/yourgrocer/image/upload/v1458622892/1458640634_star_hsrksa.png") !important; 74 | } 75 | 76 | /*----------------------------------------------------------*/ 77 | 78 | .Feedback .Feedback-mainDescription { 79 | display: block; 80 | text-align: center; 81 | } 82 | .Feedback .Feedback-description { 83 | margin: 20px 0px; 84 | line-height: 1.3em; 85 | font-weight: bold; 86 | 87 | } 88 | 89 | .Feedback-textArea { 90 | width: 100%; 91 | min-height: 100px; 92 | font-size: 16px; 93 | } 94 | 95 | input[type="submit"].Feedback-submit { 96 | background-color: #FF8903; 97 | font-size: 16px; 98 | padding: 15px 20px; 99 | display: block; 100 | margin:auto; 101 | } 102 | 103 | .Feedback-thanks { 104 | font-weight: normal; 105 | font-size: 15px; 106 | color: rgba(0, 0, 0, 0.5); 107 | } 108 | 109 | 110 | 111 | /***************************BOOK_INFO*****************************/ 112 | 113 | .photo img { 114 | width: 310px; 115 | height: 410px; 116 | border-radius: 7px; 117 | display: flex; 118 | flex-direction: column; 119 | } 120 | 121 | .profile { 122 | display: flex; 123 | background-color: #ffdbef; 124 | flex-direction: column; 125 | border-radius: 5px; 126 | padding: 20px; 127 | } 128 | 129 | .ffff { 130 | display: flex; 131 | flex-direction: row; 132 | } 133 | 134 | .hrrrrr { 135 | display: flex; 136 | flex-direction: column; 137 | padding-left: 5px; 138 | } 139 | 140 | .wrapp { 141 | display: flex; 142 | flex-direction: row; 143 | padding-left: 25px; 144 | padding-bottom: 5px; 145 | } 146 | .averageRating { 147 | color: rgb(104, 32, 62); 148 | font: Impact,"century gothic", arial, sans-serif; 149 | font-size: 35px; 150 | padding: 7px; 151 | font-weight: bold; 152 | text-align: justify; 153 | border-style: solid; 154 | border-width: 3px; 155 | border-radius: 45px; 156 | border-color: rgb(104, 32, 62); 157 | } 158 | 159 | .briefInfo { 160 | display: flex; 161 | flex-direction: column; 162 | padding-top: 35px; 163 | padding-bottom: 5px; 164 | } 165 | 166 | .main label { 167 | color: rgb(116, 14, 99); 168 | font: normal 122% Impact,"century gothic", arial, sans-serif; 169 | width: 150px; 170 | } 171 | 172 | p { 173 | color: rgb(46, 21, 42); 174 | font: Impact,"century gothic", arial, sans-serif; 175 | font-size: 21px; 176 | text-align: justify; 177 | } 178 | 179 | .bookTitle { 180 | color: rgb(150, 52, 133); 181 | text-shadow: 2px 2px #4d4557; 182 | font: normal 250% Impact,"century gothic", arial, sans-serif; 183 | } 184 | 185 | .rating { 186 | width: 400px; 187 | } 188 | 189 | /*****************/ 190 | 191 | 192 | /*sprite with stars*/ 193 | #reviewStars-input input:checked ~ label, #reviewStars-input label, #reviewStars-input label:hover, #reviewStars-input label:hover ~ label { 194 | background: url('https://pp.userapi.com/c840221/v840221309/3fef0/vD3vnj0Jc4k.jpg') no-repeat; 195 | } 196 | 197 | #reviewStars-input { 198 | border-radius: 7px; 199 | margin-top: 7px; 200 | /*fix floating problems*/ 201 | overflow: hidden; 202 | *zoom: 1; 203 | /*end of fix floating problems*/ 204 | 205 | position: relative; 206 | float: left; 207 | } 208 | 209 | #reviewStars-input input { 210 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 211 | opacity: 0; 212 | 213 | width: 43px; 214 | height: 40px; 215 | 216 | position: absolute; 217 | top: 0; 218 | z-index: 0; 219 | } 220 | 221 | #reviewStars-input input:checked ~ label { 222 | background-position: 0 -40px; 223 | height: 40px; 224 | width: 43px; 225 | } 226 | 227 | #reviewStars-input label { 228 | background-position: 0 0; 229 | height: 40px; 230 | width: 43px; 231 | float: right; 232 | cursor: pointer; 233 | margin-right: 10px; 234 | 235 | position: relative; 236 | z-index: 1; 237 | } 238 | 239 | #reviewStars-input label:hover, #reviewStars-input label:hover ~ label { 240 | background-position: 0 -40px; 241 | height: 40px; 242 | width: 43px; 243 | } 244 | 245 | #reviewStars-input #star-0 { 246 | left: 0px; 247 | } 248 | #reviewStars-input #star-1 { 249 | left: 53px; 250 | } 251 | #reviewStars-input #star-2 { 252 | left: 106px; 253 | } 254 | #reviewStars-input #star-3 { 255 | left: 159px; 256 | } 257 | #reviewStars-input #star-4 { 258 | left: 212px; 259 | } 260 | #reviewStars-input #star-5 { 261 | left: 265px; 262 | } 263 | 264 | /*********************************/ 265 | .evaluateBook { 266 | color: rgb(116, 14, 99); 267 | font-family: Impact, tahoma,verdana; 268 | font-size: 25px; 269 | } 270 | 271 | 272 | #evaluate { 273 | background-color:rgb(240, 89, 127); 274 | border: none; 275 | color: rgb(245, 245, 245); 276 | padding: 16px 22px; 277 | width: 200px; 278 | text-decoration: none; 279 | border-radius: 7px; 280 | margin: 4px 2px; 281 | cursor: pointer; 282 | font-size: 15px; 283 | 284 | } 285 | 286 | #evaluate:hover { 287 | background-color: rgb(175, 76, 97); /* Green */ 288 | color: white; 289 | box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); 290 | } 291 | 292 | 293 | .placeReview { 294 | display: flex; 295 | flex-direction: column; 296 | padding-top: 35px; 297 | padding-bottom: 5px; 298 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/booklist.css: -------------------------------------------------------------------------------- 1 | 2 | /*-----------------buttons--------------------*/ 3 | input[type="text"], input[type="password"] { 4 | width: 100%; 5 | padding: 12px 20px; 6 | margin: 8px 0; 7 | display: inline-block; 8 | border: 1px solid #ccc; 9 | border-radius: 4px; 10 | box-sizing: border-box; 11 | } 12 | 13 | 14 | .registration input[type="submit"] { 15 | width: 450px; 16 | background-color: #f22e7d; 17 | color: white; 18 | padding: 14px 20px; 19 | margin: 8px 0; 20 | border: none; 21 | border-radius: 4px; 22 | cursor: pointer; 23 | } 24 | 25 | .registration input[type="submit"]:hover { 26 | background-color: #d169e0; 27 | } 28 | 29 | .registration { 30 | border-radius: 5px; 31 | padding: 20px; 32 | } 33 | 34 | /*-----------------Birthday--------------------*/ 35 | 36 | 37 | .register { 38 | display:flex; 39 | justify-content: center; 40 | } 41 | 42 | .footer { 43 | left: 0; 44 | bottom: 0; 45 | width: 100%; 46 | height: 50px; 47 | background-color: #78778f; 48 | color: white; 49 | text-align: center; 50 | } 51 | 52 | /*-------------------------Card--------------------------*/ 53 | .cards { 54 | display: flex; 55 | flex-direction: row; 56 | flex-wrap: wrap; 57 | justify-content:center; 58 | align-content: flex-start; 59 | } 60 | .card { 61 | background-color: #f2dfe9; 62 | display: flex; 63 | flex-direction: column; 64 | justify-content:space-between; 65 | width: 250px; 66 | height: 450px; 67 | margin: 10px; 68 | padding-top: 8px; 69 | padding-bottom: 5px; 70 | border: 1px solid #ccc; 71 | box-shadow: 3px 2px 6px 0px rgba(0,0,0,0.3); 72 | } 73 | .card img { 74 | margin-left: auto; 75 | margin-right: auto; 76 | margin-top: 4px; 77 | width: 220px; 78 | height: 200px; 79 | max-width: 100%; 80 | } 81 | .card .text { 82 | padding: 0 20px 20px; 83 | } 84 | .card input[type="submit"] { 85 | align-self:flex-end; 86 | margin-left:auto; 87 | margin-right:auto; 88 | background: #f22e7d; 89 | border: 1; 90 | padding-bottom: 12px; 91 | color: white; 92 | padding: 8px; 93 | width: 100%; 94 | border-radius: 7px; 95 | } 96 | 97 | .card input[type="submit"]:hover { 98 | color: white; 99 | background: #d169e0; 100 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 101 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 102 | } 103 | 104 | .super { 105 | display: flex; 106 | flex-wrap: wrap; 107 | flex-direction: row; 108 | justify-content: center; 109 | } 110 | 111 | .superEl label{ 112 | margin-left: 5px; 113 | margin-right: 10px; 114 | font-size: 19px; 115 | margin-top: 1px; 116 | color: #783852; 117 | } 118 | 119 | .super input[type="checkbox"] { 120 | background: #555; 121 | transition: all .5s ease; 122 | cursor: pointer; 123 | } 124 | 125 | #search-btn1 { 126 | background: #dd6cac; 127 | color: #ffffff; 128 | font: "trebuchet ms", trebuchet; 129 | padding: 10px 20px; 130 | border-radius: 0 10px 10px 0; 131 | -moz-border-radius: 0 10px 10px 0; 132 | -webkit-border-radius: 0 10px 10px 0; 133 | -o-border-radius: 0 10px 10px 0; 134 | border: 0 none; 135 | width: 100px; 136 | font-weight: bold; 137 | } 138 | #search-btn1:hover { 139 | background: #be4ecf; 140 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 141 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 142 | color: white; 143 | } 144 | 145 | .searchroup{ 146 | display: flex; 147 | flex-wrap: wrap; 148 | 149 | justify-content: center; 150 | margin-left: 50px; 151 | } 152 | 153 | .searchCenter { 154 | display: flex; 155 | 156 | justify-content: center; 157 | } 158 | 159 | #create { 160 | background: #dd6cac; 161 | color: #ffffff; 162 | font: "trebuchet ms", trebuchet; 163 | padding: 10px 20px; 164 | border-radius: 10px; 165 | border: 0 none; 166 | width: 200px; 167 | font-weight: bold; 168 | } 169 | 170 | #create:hover { 171 | background: #be4ecf; 172 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 173 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 174 | color: white; 175 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sunagatov/Bookshelf/4359d2fe051354beec92004b6607b5535e12c8f4/src/main/resources/static/css/index.css -------------------------------------------------------------------------------- /src/main/resources/static/css/mainStyle.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .header { 7 | background: #f22e7d; 8 | height: 115px; 9 | padding-left: 15px; 10 | display: flex; 11 | justify-content: space-between; 12 | align-items: flex-end; 13 | } 14 | 15 | 16 | #mainButton { 17 | color: white; 18 | text-shadow: 3px 3px #4d4557; 19 | font: normal 295% "century gothic", arial, sans-serif; 20 | display: block; 21 | font-size: 3em; 22 | -webkit-margin-before: 0.57em; 23 | -webkit-margin-after: 0.67em; 24 | -webkit-margin-start: 0px; 25 | -webkit-margin-end: 0px; 26 | margin-bottom: -5px; 27 | background: #f22e7d; 28 | border: 0 none; 29 | } 30 | 31 | #mainButton:hover { 32 | color: #c574bd; 33 | } 34 | .header h1 { 35 | color: white; 36 | text-shadow: 3px 3px #4d4557; 37 | font: normal 295% "century gothic", arial, sans-serif; 38 | } 39 | 40 | .header h2 { 41 | color: white; 42 | font: normal 115% "century gothic", arial, sans-serif; 43 | } 44 | 45 | h3 { 46 | color: #783852; 47 | font: normal 110% arial, sans-serif; 48 | font-weight: bold; 49 | } 50 | p { 51 | font-size: 19px; 52 | margin-top: 1px; 53 | color: #783852; 54 | } 55 | 56 | ul { 57 | font-size: 19px; 58 | color: #783852; 59 | list-style-type: none; 60 | } 61 | /*------------Search------------------*/ 62 | 63 | #search-btn { 64 | background: #f7b2d8; 65 | color: #9b4222; 66 | font: "trebuchet ms", trebuchet; 67 | padding: 10px 20px; 68 | border-radius: 0 10px 10px 0; 69 | -moz-border-radius: 0 10px 10px 0; 70 | -webkit-border-radius: 0 10px 10px 0; 71 | -o-border-radius: 0 10px 10px 0; 72 | border: 0 none; 73 | width: 100px; 74 | font-weight: bold; 75 | } 76 | #search-btn:hover { 77 | background: #d169e0; 78 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 79 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 80 | color: white; 81 | } 82 | 83 | #search-box { 84 | background: white; 85 | padding: 10px; 86 | border-radius: 10px 0 0 10px; 87 | -moz-border-radius: 10px 0 0 10px; 88 | -webkit-border-radius: 10px 0 0 10px; 89 | -o-border-radius: 10px 0 0 10px; 90 | border: 0 none; 91 | width: 360px; 92 | } 93 | 94 | #signUpButton { 95 | color: white; 96 | padding: 5px; 97 | padding-bottom: 13px; 98 | margin-top: -9px; 99 | text-decoration: none; 100 | background: #f22e7d; 101 | border: 0 none; 102 | } 103 | 104 | #signUpButton:hover { 105 | color: #ffcffa; 106 | } 107 | 108 | 109 | .search { 110 | margin-bottom: 2px; 111 | margin-right: 15px; 112 | display: flex; 113 | flex-direction: column; 114 | } 115 | 116 | .search a { 117 | margin-top: -9px; 118 | text-decoration: none; /*убираем подчеркивание текста ссылок*/ 119 | color: white; 120 | } 121 | .search a:hover { 122 | color: #ffcffa; 123 | } 124 | .registrationButtons { 125 | display:flex; 126 | justify-content: flex-end; 127 | align-items: flex-end; 128 | } 129 | 130 | .registrationButtons a { 131 | color: white; 132 | padding: 5px; 133 | padding-bottom: 9px; 134 | } 135 | /* ------------------ top MENU ----------------- */ 136 | 137 | .navigation { 138 | display: -webkit-flex; 139 | display: flex; 140 | justify-content: center; 141 | align-content: flex-start; 142 | background: #d1cdfa; 143 | border-bottom: 3px solid #f22e7d; 144 | background-color: #f7b2d8; 145 | } 146 | 147 | .navigation ul { 148 | display: flex; 149 | list-style: none; /*убираем маркеры списка*/ 150 | margin: -6px; /*убираем верхнее и нижнее поле, равное 1em*/ 151 | padding-left: 0; /*убираем левый отступ, равный 40px*/ 152 | } 153 | 154 | #menuButton { 155 | text-decoration: none; /*убираем подчеркивание текста ссылок*/ 156 | padding: 1rem 3rem; 157 | width: 49px; 158 | display: block; 159 | color: #9b4222; 160 | background: #ffdbef; 161 | border: 2px solid #bf3069; 162 | border-radius: 7px; 163 | width: 150px; 164 | } 165 | #menuButton:hover { 166 | color: white; 167 | background: #d169e0; 168 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 169 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 170 | } 171 | 172 | #menuButton2:hover { 173 | color: white; 174 | background: #d169e0; 175 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 176 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 177 | } 178 | 179 | .navigation a { 180 | text-decoration: none; /*убираем подчеркивание текста ссылок*/ 181 | padding: 1rem 3rem; 182 | width: 49px; 183 | display: block; 184 | color: #9b4222; 185 | background: #ffdbef; 186 | border: 2px solid #bf3069; 187 | border-radius: 7px; 188 | } 189 | .navigation a:hover { 190 | color: white; 191 | background: #d169e0; 192 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 193 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 194 | } 195 | 196 | .navigation li { 197 | font-color: white; 198 | length: 50px; 199 | margin-right: 8px; 200 | } 201 | 202 | 203 | /*-----------------------------Main-----------------------------*/ 204 | .main { 205 | min-height: 900px; 206 | width: 900px; 207 | margin-right: auto; 208 | margin-left: auto; 209 | background-color: #f7b2d8; 210 | padding: 15px; 211 | border: 1px solid #ccc; 212 | border-radius: 7px; 213 | margin-top: 27px; 214 | margin-bottom: 27px; 215 | } 216 | 217 | body { 218 | background-color: #ffdbef; 219 | line-height: 1.5; 220 | } 221 | 222 | footer { 223 | left: 0; 224 | bottom: 0; 225 | width: 100%; 226 | height: 30px; 227 | background-color: #f22e7d; 228 | color: white; 229 | text-align: center; 230 | } 231 | 232 | /*-----------------Registration--------------------*/ 233 | 234 | .registration { 235 | background-color: #ffdbef; 236 | display: flex; 237 | flex-direction: column; 238 | border-radius: 5px; 239 | padding: 20px; 240 | } 241 | 242 | .registration h2 { 243 | color: grey; 244 | text-shadow: 2px 2px #4d4557; 245 | font: normal 250% "century gothic", arial, sans-serif; 246 | } 247 | 248 | hr { 249 | margin-top: 11px; 250 | margin-bottom: 15px; 251 | } 252 | 253 | input[type="text"], input[type="password"], input[type="email"] { 254 | width: 100%; 255 | padding: 12px 20px; 256 | margin: 8px 0; 257 | display: inline-block; 258 | border: 1px solid #ccc; 259 | border-radius: 4px; 260 | box-sizing: border-box; 261 | } 262 | 263 | .profile { 264 | display: flex; 265 | flex-direction: column; 266 | } 267 | 268 | /****************************************************************************************/ 269 | /* Full-width input fields */ 270 | .modal input[type=text], input[type=password] { 271 | width: 100%; 272 | padding: 12px 20px; 273 | margin: 8px 0; 274 | display: inline-block; 275 | border: 1px solid #ccc; 276 | box-sizing: border-box; 277 | } 278 | 279 | /* Set a style for all buttons */ 280 | .modal button { 281 | background-color: rgb(235, 137, 187); 282 | color: white; 283 | padding: 11px 14px; 284 | margin: 4px 0; 285 | margin-right: 6px; 286 | border-radius: 4px; 287 | cursor: pointer; 288 | width: 100px; 289 | } 290 | 291 | .modal button:hover { 292 | background-color: rgb(214, 77, 146); 293 | opacity: 0.8; 294 | } 295 | 296 | /* Extra styles for the cancel button */ 297 | .cancelbtn { 298 | width: auto; 299 | padding: 10px 18px; 300 | background-color: #9b194f; 301 | } 302 | 303 | /* Center the image and position the close button */ 304 | .imgcontainer { 305 | text-align: center; 306 | margin: 24px 0 12px 0; 307 | position: relative; 308 | } 309 | 310 | img.avatar { 311 | width: 40%; 312 | border-radius: 50%; 313 | } 314 | 315 | .container { 316 | padding: 16px; 317 | background-color: #f7d9e6; 318 | } 319 | 320 | span.psw { 321 | float: right; 322 | padding-top: 16px; 323 | } 324 | 325 | /* The Modal (background) */ 326 | .modal { 327 | display: none; /* Hidden by default */ 328 | position: fixed; /* Stay in place */ 329 | z-index: 1; /* Sit on top */ 330 | left: 0; 331 | top: 0; 332 | color: #50281a; 333 | width: 100%; /* Full width */ 334 | height: 100%; /* Full height */ 335 | overflow: auto; /* Enable scroll if needed */ 336 | background-color: rgb(0,0,0); /* Fallback color */ 337 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 338 | padding-top: 60px; 339 | } 340 | 341 | /* Modal Content/Box */ 342 | .modal-content { 343 | background-color: #f7d9e6; 344 | margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */ 345 | border: 1px solid #888; 346 | border-radius: 5px; 347 | width: 40%; /* Could be more or less, depending on screen size */ 348 | } 349 | 350 | /* The Close Button (x) */ 351 | .close { 352 | position: absolute; 353 | right: 25px; 354 | top: 0; 355 | color: #000; 356 | font-size: 35px; 357 | font-weight: bold; 358 | } 359 | 360 | .close:hover, 361 | .close:focus { 362 | color: red; 363 | cursor: pointer; 364 | } 365 | 366 | /* Add Zoom Animation */ 367 | .animate { 368 | -webkit-animation: animatezoom 0.6s; 369 | animation: animatezoom 0.6s 370 | } 371 | 372 | @-webkit-keyframes animatezoom { 373 | from {-webkit-transform: scale(0)} 374 | to {-webkit-transform: scale(1)} 375 | } 376 | 377 | @keyframes animatezoom { 378 | from {transform: scale(0)} 379 | to {transform: scale(1)} 380 | } 381 | 382 | /* Change styles for span and cancel button on extra small screens */ 383 | @media screen and (max-width: 300px) { 384 | span.psw { 385 | display: block; 386 | float: none; 387 | } 388 | .cancelbtn { 389 | width: 100%; 390 | } 391 | } 392 | 393 | .super { 394 | display: flex; 395 | flex-direction: column; 396 | 397 | height:50px; 398 | } 399 | .super input[type=checkbox] { 400 | margin-right: 7px; 401 | } 402 | 403 | .addNewItem [type="submit"] { 404 | width: 450px; 405 | background-color: #f22e7d; 406 | color: white; 407 | padding: 14px 20px; 408 | margin: 8px 0; 409 | border: none; 410 | border-radius: 4px; 411 | cursor: pointer; 412 | } 413 | -------------------------------------------------------------------------------- /src/main/resources/static/css/profile.css: -------------------------------------------------------------------------------- 1 | 2 | .firstName { 3 | background: #ffdbef; 4 | border: 1px solid rgb(206, 66, 136); 5 | border-radius: 7px; 6 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/registration.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /*-----------------buttons--------------------*/ 4 | input[type="text"], input[type="password"], input[type="email"] { 5 | width: 100%; 6 | padding: 12px 20px; 7 | margin: 8px 0; 8 | display: inline-block; 9 | border: 1px solid #ccc; 10 | border-radius: 4px; 11 | box-sizing: border-box; 12 | } 13 | 14 | .SubmitButton input[type="submit"] { 15 | width: 450px; 16 | background-color: #f22e7d; 17 | color: white; 18 | padding: 14px 20px; 19 | margin: 8px 0; 20 | border: none; 21 | border-radius: 4px; 22 | cursor: pointer; 23 | } 24 | 25 | .registration input[type="submit"]:hover { 26 | background-color: #d169e0; 27 | } 28 | 29 | .gender { 30 | display:flex; 31 | flex-direction: column; 32 | justify-content: flax-start; 33 | } 34 | .gender input[type="radio"] { 35 | margin-right:4px; 36 | margin-top:7px; 37 | padding: 12px 20px; 38 | } 39 | 40 | .female { 41 | margin-bottom:3px; 42 | } 43 | 44 | select { 45 | padding: 12px 20px; 46 | margin: 8px 0; 47 | border: 1px solid #ccc; 48 | border-radius: 4px; 49 | box-sizing: border-box; 50 | } 51 | .publicationDate { 52 | display:flex; 53 | flex-direction: row; 54 | justify-content: space-between; 55 | } 56 | .publicationDate_Day select { 57 | width: 280px; 58 | margin-right: 10px; 59 | } 60 | 61 | .publicationDate_Month select { 62 | margin-right: 10px; 63 | width: 280px; 64 | } 65 | .publicationDate_Year select { 66 | width: 280px; 67 | } 68 | 69 | textarea { 70 | width: 100%; 71 | height: 150px; 72 | padding: 12px 20px; 73 | margin: 8px 0; 74 | display: inline-block; 75 | border: 1px solid #ccc; 76 | border-radius: 4px; 77 | box-sizing: border-box; 78 | } 79 | 80 | 81 | 82 | /*-----------------Birthday--------------------*/ 83 | 84 | .birthday { 85 | display: -webkit-flex; 86 | display: flex; 87 | justify-content: flex-start; 88 | align-content: flex-start; 89 | } 90 | 91 | .SubmitButton { 92 | display:flex; 93 | justify-content: center; 94 | } 95 | 96 | 97 | 98 | .downloadPicture input[type="submit"] { 99 | width: 150px; 100 | background-color: #f897c0; 101 | color: white; 102 | padding: 15px 18px; 103 | margin: 8px 0; 104 | border: 1px solid rgb(224, 75, 119); 105 | border-radius: 4px; 106 | cursor: pointer; 107 | } 108 | 109 | 110 | .downloadPicture input[type="file"] { 111 | display: none; 112 | 113 | } 114 | .custom-file-upload { 115 | border: 1px solid rgb(224, 75, 119); 116 | color: white; 117 | background-color: #f897c0; 118 | display: inline-block; 119 | border-radius: 4px; 120 | padding: 11px 14px; 121 | cursor: pointer; 122 | } 123 | -------------------------------------------------------------------------------- /src/main/resources/static/css/review.css: -------------------------------------------------------------------------------- 1 | .review { 2 | display: flex; 3 | flex-direction: column; 4 | background-color: #daf1db; 5 | flex-direction: column; 6 | border-radius: 5px; 7 | padding: 20px; 8 | margin: 30px; 9 | border-style: solid; 10 | border-width: 2px; 11 | border-radius: 5px; 12 | border-color: rgb(104, 32, 62); 13 | } 14 | 15 | .author { 16 | display: flex; 17 | flex-direction: column; 18 | } 19 | 20 | .author a { 21 | color: rgb(177, 25, 88); 22 | cursor: pointer; 23 | font-size: 45px; 24 | } 25 | 26 | .review img { 27 | width: 170px; 28 | height: 150px; 29 | border-radius: 7px; 30 | } 31 | 32 | 33 | .reviewTitle { 34 | color: rgb(148, 42, 42); 35 | font-family: Impact, tahoma,verdana; 36 | font-size: 28px; 37 | margin-bottom: 10px; 38 | } 39 | 40 | .headerReview { 41 | display: flex; 42 | flex-direction: row; 43 | justify-content: flex-start; 44 | margin-bottom: 40px; 45 | } 46 | 47 | .thisBookReviews { 48 | text-align: center; 49 | color: rgb(86, 33, 33); 50 | font-family: Impact, tahoma,verdana; 51 | font-weight: bold; 52 | margin-top: 50px; 53 | font-size: 35px; 54 | } 55 | 56 | 57 | 58 | .reviewInfo { 59 | display: flex; 60 | flex-direction: column; 61 | color: rgb(86, 33, 33); 62 | width: 400px; 63 | font-family: Impact, tahoma,verdana; 64 | text-align: left; 65 | margin-top: 60px; 66 | margin-right: 40px; 67 | font-size: 18px; 68 | padding: 4px 0 5px 0; 69 | } 70 | 71 | .reviewInfo label { 72 | color: rgb(32, 10, 29); 73 | width:170px; 74 | } 75 | 76 | 77 | .reviewInfoWrapp { 78 | display: flex; 79 | flex-direction: row; 80 | margin-bottom: 7px; 81 | margin-left: 40px; 82 | } 83 | 84 | .wrappValue { 85 | width:200px; 86 | margin-left: 20px; 87 | } 88 | -------------------------------------------------------------------------------- /src/main/resources/static/css/userProfile.css: -------------------------------------------------------------------------------- 1 | .photo img { 2 | width: 310px; 3 | height: 410px; 4 | border-radius: 7px; 5 | } 6 | 7 | .profile { 8 | display: flex; 9 | background-color: #ffdbef; 10 | flex-direction: column; 11 | border-radius: 5px; 12 | padding: 20px; 13 | } 14 | 15 | .ffff { 16 | display: flex; 17 | flex-direction: row; 18 | } 19 | 20 | .hrrrrr { 21 | display: flex; 22 | flex-direction: column; 23 | padding-left: 5px; 24 | } 25 | 26 | .wrapp { 27 | display: flex; 28 | flex-direction: row; 29 | padding-left: 25px; 30 | padding-bottom: 5px; 31 | } 32 | 33 | .wrapp1 { 34 | display: flex; 35 | flex-direction: row; 36 | padding-bottom: 5px; 37 | } 38 | 39 | .wrapp1 label { 40 | color: rgb(116, 14, 99); 41 | font: normal 110% Impact,"century gothic", arial, sans-serif; 42 | width: 150px; 43 | } 44 | 45 | .biography { 46 | display: flex; 47 | flex-direction: column; 48 | padding-top: 25px; 49 | padding-left: 10px; 50 | padding-bottom: 5px; 51 | } 52 | 53 | .wrapp label { 54 | color: rgb(116, 14, 99); 55 | font: normal 110% Impact,"century gothic", arial, sans-serif; 56 | width: 150px; 57 | } 58 | 59 | p { 60 | color: rgb(46, 21, 42); 61 | font: Impact,"century gothic", arial, sans-serif; 62 | font-size: 20px; 63 | text-align: justify; 64 | } 65 | 66 | .profile h2 { 67 | color: rgb(150, 52, 133); 68 | text-shadow: 2px 2px #4d4557; 69 | font: normal 250% Impact,"century gothic", arial, sans-serif; 70 | } -------------------------------------------------------------------------------- /src/main/resources/templates/crud/addAuthorView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | New author 9 | 10 | 11 | 12 | 13 |
14 | 20 | 25 |
26 | 27 | 41 | 42 |
43 |
44 |
45 | 46 |

Add new author

47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 71 | 72 | 73 |
74 | 75 |
76 | 77 | 78 |
79 |
80 | 81 |
82 |
83 | 84 |
85 | 86 |
87 |
88 |
89 |
90 |
91 | Made by Zufar 92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/resources/templates/crud/addBookView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | New book 9 | 10 | 11 | 12 | 13 |
14 | 20 | 25 |
26 | 27 | 28 | 42 | 43 |
44 |
45 |
46 | 47 |

Add new book

48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 |
85 | 87 |
88 |
89 | 90 |
91 | 92 |
93 |
94 |
95 |
96 |
97 | Made by Zufar 98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /src/main/resources/templates/crud/updateAuthorView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 20 | 25 |
26 | 27 | 28 | 42 | 43 |
44 |
45 |
46 | 47 |

Update an author

48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 72 | 73 | 74 |
75 | 76 |
77 | 78 | 79 |
80 |
81 | 82 |
83 |
84 | 85 |
86 | 87 |
88 |
89 |
90 |
91 |
92 | Made by Zufar 93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/resources/templates/crud/updateBookView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 20 | 25 |
26 | 27 | 28 | 42 | 43 |
44 |
45 |
46 | 47 |

Update a book

48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 |
83 | 85 |
86 |
87 | 88 |
89 | 90 |
91 |
92 |
93 |
94 |
95 | Made by Zufar 96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/resources/templates/error/error-404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Sorry, we couldn't find the page you were looking for.

5 |

Go Home

6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/templates/error/error-500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Sorry, something went wrong!

5 | 6 |

We're fixing it.

7 |

Go Home

8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Home 7 | 8 | 9 | 10 | 11 |
12 | 18 | 31 |
32 | 33 | 34 | 48 | 49 |
50 |

Book Search is a book site for users of all ages

51 |

Welcome to Book Search - a large book portal dedicated to literature and the selection of booksIds. 52 | Not sure what to do? Read booksIds! Use the search for booksIds on the site, with which you will find exactly 53 | what 54 | suits you, and a flexible rating system will allow you to choose from what you find - the best.

55 |

World bestsellers, the best new products, favorite works of world importance - all of this is collected from us 56 | on 57 | Book Search. Here are not only the best works of all times and peoples, but also a complete 58 | information about authors who are loved by readers 59 | all over the world.

60 |

Book genres on Book Search

61 |

The site about booksIds - Book Search - offers a wide range of literary works. It allows everyone 62 | find what he likes. Most often, readers turn to such genres as:

63 |
    64 |
  • - classic works;
  • 65 |
  • - detective;
  • 66 |
  • - business booksIds;
  • 67 |
  • - self improvement booksIds;
  • 68 |
  • - fantasy;
  • 69 |
  • - fiction and other booksIds;
  • 70 |
71 |

The location of e-booksIds is such that finding the right version of reading is quite simple. In addition, the 72 | site 73 | provides readers with lists of the most interesting works presented on the portal. Similar ratings compiled by 74 | voting our users.

75 |

Lists of the most interesting and popular book copies are constantly updated, updated with new ones, recently 76 | submitted to a wide readership. Leave a review and describe your own feelings after users reading the works may 77 | go to the book page. In addition, we have a list of booksIds that everyone should read.

78 |

Fragments of works of world literature are available in electronic form in the following formats:

79 |
    80 |
  • - EPUB;
  • 81 |
  • - FB2;
  • 82 |
  • - PDF;
  • 83 |
  • - TXT.
  • 84 |
85 |

Register on the BookSearch portal, and you will easily find interesting literature for you personally, as well as 86 | choose what to advise to read to friends and family. Find popular booksIds, leave reviews, make your personal 87 | library 88 | invite your friends! Let's collect all reading people in one place!

89 |
90 |
91 | Made by Zufar 92 |
93 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/resources/templates/lists/authorListView.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Author list 10 | 11 | 12 | 13 | 14 |
15 | 21 | 31 |
32 | 33 | 42 | 43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 |
54 | 55 | Sample photo 56 | 57 |
58 |
59 |

Full Name:

60 |

61 | 62 |

Nickname:

63 |

64 | 65 |

Country:

66 |

67 |
68 | 69 |
70 |
71 | 72 |
73 |
74 | 75 |
76 |
78 |
79 |
80 | 81 |
82 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Made by Zufar 93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/resources/templates/lists/bookListView.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Book list 10 | 11 | 12 | 13 | 14 |
15 | 21 | 31 |
32 | 33 | 42 | 43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 | 53 | Sample photo 54 | 55 |
56 | 57 |
58 |

Title:

59 |

60 | 61 |

Publication date:

62 |

63 | 64 |

Country:

65 |

66 |
67 | 68 |
69 |
70 | 71 |
72 |
73 | 74 |
75 |
76 | 77 |
78 |
79 | 80 |
81 |
82 | 83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | Made by Zufar 92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Login 8 | 9 | 10 |
11 | 12 |
13 |
14 |

Sign in

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |

Back to home page

27 | 28 |
29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/templates/profiles/authorProfileView.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 21 | 31 |
32 | 33 | 47 | 48 |
49 |
50 | 51 |

52 |
53 | 54 |
55 | 56 |
57 | userPhoto 58 |
59 | 60 |
61 |
62 | 63 |
64 |


65 |
66 |
67 | 68 |
69 | 70 |

71 |
72 | 73 |
74 | 75 |

76 |
77 | 78 |
79 | 80 |

81 |
82 | 83 |
84 | 85 |

86 |
87 |
88 |
89 |
90 | 91 |
92 | 93 |
94 |

95 | Info About authorInfo About authorInfo About authorInfo About authorInfo About authorInfo About author. 96 | Info About authorInfo About authorInfo About authorInfo About authorInfo About authorInfo About author. 97 | Info About authorInfo About authorInfo About authorInfo About authorInfo About authorInfo About author. 98 | Info About authorInfo About authorInfo About authorInfo About authorInfo About authorInfo About author. 99 |

100 |
101 |
102 |
103 |
104 | Made by Zufar 105 |
106 | 107 | 108 | -------------------------------------------------------------------------------- /src/main/resources/templates/profiles/bookProfileView.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 21 | 31 |
32 | 33 | 47 | 48 |
49 |
50 | 51 |

52 |
53 | 54 |
55 |
56 | bookPhoto 57 |
58 | 59 |
60 |
61 | 62 |
63 |


64 |
65 |
66 | 67 |
68 | 69 |

70 |
71 | 72 |
73 | 74 |

75 |
76 | 77 |
78 | 79 |

80 |
81 | 82 |
83 | PDF 85 |
86 | 87 |
88 | EPUB 90 |
91 | 92 |
93 | FB2 95 |
96 |
97 |
98 |
99 |
100 |
101 | 102 |
103 |

104 | Info About book Info About book Info About book Info About book Info About book Info About book. 105 | Info About book Info About book Info About book Info About book Info About book Info About book. 106 | Info About book Info About book Info About book Info About book Info About book Info About book. 107 | Info About book Info About book Info About book Info About book Info About book Info About book. 108 |

109 |
110 |
111 | 112 |
113 | Made by Zufar 114 |
115 | 116 | -------------------------------------------------------------------------------- /src/main/resources/templates/registrationView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Registration 9 | 10 | 11 | 12 |
13 | 19 |
20 | 21 | 35 |
36 |
37 |
38 | 39 |

Registration form

40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 |
52 | 53 | 54 | 59 | 60 | 61 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 |
77 | 78 |
79 |
80 |
81 |
82 | 85 | 86 | -------------------------------------------------------------------------------- /src/test/java/com/zufar/bookshelf/BookshelfApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.zufar.bookshelf; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BookshelfApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------