diff --git a/build.gradle b/build.gradle index d734261..9b3c9ec 100644 --- a/build.gradle +++ b/build.gradle @@ -20,13 +20,16 @@ repositories { } dependencies { + implementation 'org.springframework.data:spring-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'mysql:mysql-connector-java' + runtimeOnly 'org.postgresql:postgresql' + implementation 'com.zaxxer:HikariCP:5.0.1' annotationProcessor 'org.projectlombok:lombok' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/site/easystartup/easystartupcore/config/AppConfig.java b/src/main/java/site/easystartup/easystartupcore/config/AppConfig.java index 72de475..4abc860 100644 --- a/src/main/java/site/easystartup/easystartupcore/config/AppConfig.java +++ b/src/main/java/site/easystartup/easystartupcore/config/AppConfig.java @@ -1,4 +1,16 @@ package site.easystartup.easystartupcore.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@ComponentScan(basePackages = {"site.easystartup.easystartupcore"}) public class AppConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/src/main/java/site/easystartup/easystartupcore/config/MvcConfig.java b/src/main/java/site/easystartup/easystartupcore/config/MvcConfig.java new file mode 100644 index 0000000..d1736f6 --- /dev/null +++ b/src/main/java/site/easystartup/easystartupcore/config/MvcConfig.java @@ -0,0 +1,12 @@ +package site.easystartup.easystartupcore.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class MvcConfig implements WebMvcConfigurer { + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); + } +} diff --git a/src/main/java/site/easystartup/easystartupcore/config/WebSecurityConfig.java b/src/main/java/site/easystartup/easystartupcore/config/WebSecurityConfig.java new file mode 100644 index 0000000..ff5810d --- /dev/null +++ b/src/main/java/site/easystartup/easystartupcore/config/WebSecurityConfig.java @@ -0,0 +1,45 @@ +package site.easystartup.easystartupcore.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.password.PasswordEncoder; + +import javax.sql.DataSource; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + private final DataSource dataSource; + + private final PasswordEncoder passwordEncoder; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/", "/home", "/registration").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .and() + .logout() + .permitAll(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.jdbcAuthentication() + .dataSource(dataSource) + .passwordEncoder(passwordEncoder) + .usersByUsernameQuery("SELECT `username`, `password`, `active` FROM usr WHERE `username` = ?;") + .authoritiesByUsernameQuery("SELECT u.username, ur.roles FROM usr u INNER JOIN user_role ur ON u.id = ur.user_id WHERE username = ?;"); + } +} diff --git a/src/main/java/site/easystartup/easystartupcore/controller/RegistrationController.java b/src/main/java/site/easystartup/easystartupcore/controller/RegistrationController.java new file mode 100644 index 0000000..4048f45 --- /dev/null +++ b/src/main/java/site/easystartup/easystartupcore/controller/RegistrationController.java @@ -0,0 +1,45 @@ +package site.easystartup.easystartupcore.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import site.easystartup.easystartupcore.domain.Role; +import site.easystartup.easystartupcore.domain.User; +import site.easystartup.easystartupcore.repos.UserRepo; + +import java.util.Collections; + +@Controller +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class RegistrationController { + private final PasswordEncoder passwordEncoder; + + private final UserRepo userRepo; + + @GetMapping("/registration") + public String registration() { + return "registration"; + } + + @PostMapping("/registration") + public String addUser(User user, Model model) { + User userFromDb = userRepo.findByUsername(user.getUsername()); + + if(userFromDb != null) { + model.addAttribute("message", "Пользователь уже существует!"); + return "registration"; + } + + user.setActive(true); + user.setRoles(Collections.singleton(Role.USER)); + user.setPassword(passwordEncoder.encode(user.getPassword())); + + userRepo.save(user); + + return "redirect:/login"; + } +} diff --git a/src/main/java/site/easystartup/easystartupcore/domain/Role.java b/src/main/java/site/easystartup/easystartupcore/domain/Role.java new file mode 100644 index 0000000..1f8a7ef --- /dev/null +++ b/src/main/java/site/easystartup/easystartupcore/domain/Role.java @@ -0,0 +1,5 @@ +package site.easystartup.easystartupcore.domain; + +public enum Role { + USER, PREMIUM, MODER, ADMIN; +} diff --git a/src/main/java/site/easystartup/easystartupcore/domain/User.java b/src/main/java/site/easystartup/easystartupcore/domain/User.java new file mode 100644 index 0000000..6d8fd49 --- /dev/null +++ b/src/main/java/site/easystartup/easystartupcore/domain/User.java @@ -0,0 +1,24 @@ +package site.easystartup.easystartupcore.domain; + +import lombok.*; + +import javax.persistence.*; +import java.util.Set; + +@Data +@Entity +@Table(name = "usr") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + private String username; + private String password; + private boolean active; + + @ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER) + @CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id")) + @Enumerated(EnumType.STRING) + private Set roles; + +} diff --git a/src/main/java/site/easystartup/easystartupcore/repos/UserRepo.java b/src/main/java/site/easystartup/easystartupcore/repos/UserRepo.java new file mode 100644 index 0000000..d716224 --- /dev/null +++ b/src/main/java/site/easystartup/easystartupcore/repos/UserRepo.java @@ -0,0 +1,8 @@ +package site.easystartup.easystartupcore.repos; + +import org.springframework.data.jpa.repository.JpaRepository; +import site.easystartup.easystartupcore.domain.User; + +public interface UserRepo extends JpaRepository { + User findByUsername(String username); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 949105b..96ccdf5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,14 +1,11 @@ server.port=80 -spring.main.banner-mode=off -logging.level.org.springframework=ERROR - -spring.jpa.hibernate.ddl-auto=none +spring.jpa.hibernate.ddl-auto=create spring.sql.init.mode=always + spring.sql.init.platform=postgres spring.datasource.url=jdbc:postgresql://localhost:5432/testdb spring.datasource.username=postgres spring.datasource.password=root -spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true diff --git a/src/main/resources/static/logo/logo_black.png b/src/main/resources/static/logo/logo_black.png new file mode 100644 index 0000000..01e76af Binary files /dev/null and b/src/main/resources/static/logo/logo_black.png differ diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..5e73206 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,65 @@ + + + + + Вход + + + + + + + + +
+ +
+

Пожалуйста, войдите

+ +
+ + +
+
+ + +
+ +
+ +
+ + + +
+ Неправильный логин или пароль. +
+
+ Вы вышли из аккаунта. +
+ +

© 2022

+
+ Зарегистрироваться +
+ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/registration.html b/src/main/resources/templates/registration.html new file mode 100644 index 0000000..3ce7d41 --- /dev/null +++ b/src/main/resources/templates/registration.html @@ -0,0 +1,42 @@ + + + + Регистрация + + + + + + + + + + +
+ +
+ +

Пожалуйста, зарегистрируйтесь

+ +
+ + +
+
+ + +
+ + +
+

+
+

© 2022

+
+
+ + + + \ No newline at end of file diff --git a/src/test/java/site/easystartup/easystartupcore/FileUploadTests.java b/src/test/java/site/easystartup/easystartupcore/FileUploadTests.java new file mode 100644 index 0000000..2600fed --- /dev/null +++ b/src/test/java/site/easystartup/easystartupcore/FileUploadTests.java @@ -0,0 +1,68 @@ +package site.easystartup.easystartupcore; + +import java.nio.file.Paths; +import java.util.stream.Stream; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import site.easystartup.easystartupcore.storage.StorageFileNotFoundException; +import site.easystartup.easystartupcore.storage.StorageService; + +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@AutoConfigureMockMvc +@SpringBootTest +public class FileUploadTests { + + @Autowired + private MockMvc mvc; + + @MockBean + private StorageService storageService; + + @Test + public void shouldListAllFiles() throws Exception { + given(this.storageService.loadAll()) + .willReturn(Stream.of(Paths.get("first.txt"), Paths.get("second.txt"))); + + this.mvc.perform(get("/")).andExpect(status().isOk()) + .andExpect(model().attribute("files", + Matchers.contains("http://localhost/files/first.txt", + "http://localhost/files/second.txt"))); + } + + @Test + public void shouldSaveUploadedFile() throws Exception { + MockMultipartFile multipartFile = new MockMultipartFile("file", "test.txt", + "text/plain", "Spring Framework".getBytes()); + this.mvc.perform(multipart("/").file(multipartFile)) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "/")); + + then(this.storageService).should().save(multipartFile); + } + + @SuppressWarnings("unchecked") + @Test + public void should404WhenMissingFile() throws Exception { + given(this.storageService.load("test.txt")) + .willThrow(StorageFileNotFoundException.class); + + this.mvc.perform(get("/files/test.txt")).andExpect(status().isNotFound()); + } + +} \ No newline at end of file