diff options
| author | realtradam <[email protected]> | 2024-07-13 00:28:07 -0400 |
|---|---|---|
| committer | realtradam <[email protected]> | 2024-07-13 00:28:07 -0400 |
| commit | 3ea4cd2f9b3808ef645092816d888de406580e6d (patch) | |
| tree | 81cc8005f1ae329bd1cbb65def26b53ff495134d | |
| parent | d0e45a9093b33d4e5cb5f57fabdcb807dc8e8ff0 (diff) | |
| download | spring-blog-3ea4cd2f9b3808ef645092816d888de406580e6d.tar.gz spring-blog-3ea4cd2f9b3808ef645092816d888de406580e6d.zip | |
implement user registration
| -rw-r--r-- | pom.xml | 4 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/controllers/ArticleController.java | 6 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/controllers/AuthController.java | 64 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/dto/RegistrationDto.java | 18 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/models/Role.java | 26 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/models/UserEntity.java | 32 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/repository/RoleRepository.java | 8 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/repository/UserRepository.java | 9 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/services/ArticleService.java | 1 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/services/UserService.java | 13 | ||||
| -rw-r--r-- | src/main/java/com/blog/web/services/impl/UserServiceImpl.java | 46 | ||||
| -rw-r--r-- | src/main/resources/application.properties | 2 | ||||
| -rw-r--r-- | src/main/resources/templates/auth/login.html | 48 | ||||
| -rw-r--r-- | src/main/resources/templates/auth/register.html | 63 | ||||
| -rw-r--r-- | src/main/resources/templates/index.html | 2 | ||||
| -rw-r--r-- | src/main/resources/templates/layout.html | 4 |
16 files changed, 341 insertions, 5 deletions
@@ -72,6 +72,10 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> </dependencies> <build> diff --git a/src/main/java/com/blog/web/controllers/ArticleController.java b/src/main/java/com/blog/web/controllers/ArticleController.java index 0cdff15..fc18dd7 100644 --- a/src/main/java/com/blog/web/controllers/ArticleController.java +++ b/src/main/java/com/blog/web/controllers/ArticleController.java @@ -20,7 +20,7 @@ public class ArticleController { this.articleService = articleService; } - @GetMapping("/") + @GetMapping("/articles") public String listArticles(Model model) { List<ArticleDto> articles = articleService.findAllArticles(); model.addAttribute("articles", articles); @@ -85,8 +85,8 @@ public class ArticleController { return "index"; } - @GetMapping("/articles") + @GetMapping("/") public String getArticles() { - return "redirect:/"; + return "redirect:/articles"; } } diff --git a/src/main/java/com/blog/web/controllers/AuthController.java b/src/main/java/com/blog/web/controllers/AuthController.java new file mode 100644 index 0000000..bb7cb0d --- /dev/null +++ b/src/main/java/com/blog/web/controllers/AuthController.java @@ -0,0 +1,64 @@ +package com.blog.web.controllers; + +import com.blog.web.dto.RegistrationDto; +import com.blog.web.models.UserEntity; +import com.blog.web.services.UserService; +import jakarta.validation.Valid; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; + +@Controller +public class AuthController { + private UserService userService; + + public AuthController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/login") + public String loginPage() { + return "auth/login"; + } + + @GetMapping("/register") + public String getRegisterForm(Model model) { + RegistrationDto user = new RegistrationDto(); + model.addAttribute("user", user); + return "auth/register"; + } + + @PostMapping("/register/save") + public String register(@Valid @ModelAttribute("user")RegistrationDto user, + BindingResult result, + Model model) { + UserEntity existingUserEmail = userService.findByEmail(user.getEmail()); + if( + existingUserEmail != null && + existingUserEmail.getEmail() != null && + !existingUserEmail.getEmail().isEmpty() + ) { + result.rejectValue("email", "There is already a user with this email"); + } + + UserEntity existingUsername = userService.findByUsername(user.getUsername()); + if( + existingUsername != null && + existingUsername.getUsername() != null && + !existingUsername.getUsername().isEmpty() + ) + { + result.rejectValue("username", "There is already a user with this username"); + } + + if(result.hasErrors()) { + model.addAttribute("user", user); + return "register"; + } + userService.saveUser(user); + return "redirect:/articles?success"; + } +} diff --git a/src/main/java/com/blog/web/dto/RegistrationDto.java b/src/main/java/com/blog/web/dto/RegistrationDto.java new file mode 100644 index 0000000..1682c5c --- /dev/null +++ b/src/main/java/com/blog/web/dto/RegistrationDto.java @@ -0,0 +1,18 @@ +package com.blog.web.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +@Data +public class RegistrationDto { + private Long id; + @NotEmpty + private String username; + @NotEmpty + private String email; + @NotEmpty + private String password; +} diff --git a/src/main/java/com/blog/web/models/Role.java b/src/main/java/com/blog/web/models/Role.java new file mode 100644 index 0000000..b04c9d8 --- /dev/null +++ b/src/main/java/com/blog/web/models/Role.java @@ -0,0 +1,26 @@ +package com.blog.web.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.catalina.User; + +import java.sql.Array; +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity(name = "roles") +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + @ManyToMany(mappedBy = "roles") + private List<UserEntity> users = new ArrayList<>(); +} diff --git a/src/main/java/com/blog/web/models/UserEntity.java b/src/main/java/com/blog/web/models/UserEntity.java new file mode 100644 index 0000000..2dfb036 --- /dev/null +++ b/src/main/java/com/blog/web/models/UserEntity.java @@ -0,0 +1,32 @@ +package com.blog.web.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity(name = "users") +// Named UserEntity to prevent conflicts with Java User object +public class UserEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String username; + private String email; + private String password; + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable( + name = "user_roles", + joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, + inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")} + ) + private List<Role> roles = new ArrayList<>(); +} diff --git a/src/main/java/com/blog/web/repository/RoleRepository.java b/src/main/java/com/blog/web/repository/RoleRepository.java new file mode 100644 index 0000000..08c9ef4 --- /dev/null +++ b/src/main/java/com/blog/web/repository/RoleRepository.java @@ -0,0 +1,8 @@ +package com.blog.web.repository; + +import com.blog.web.models.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RoleRepository extends JpaRepository<Role, Long> { + Role findByName(String name); +} diff --git a/src/main/java/com/blog/web/repository/UserRepository.java b/src/main/java/com/blog/web/repository/UserRepository.java new file mode 100644 index 0000000..af67f58 --- /dev/null +++ b/src/main/java/com/blog/web/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.blog.web.repository; + +import com.blog.web.models.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository<UserEntity, Long> { + UserEntity findByEmail(String email); + UserEntity findByUsername(String username); +} diff --git a/src/main/java/com/blog/web/services/ArticleService.java b/src/main/java/com/blog/web/services/ArticleService.java index 9cab70b..2c1e9f4 100644 --- a/src/main/java/com/blog/web/services/ArticleService.java +++ b/src/main/java/com/blog/web/services/ArticleService.java @@ -5,6 +5,7 @@ import com.blog.web.models.Article; import java.util.List; + public interface ArticleService { List<ArticleDto> findAllArticles(); diff --git a/src/main/java/com/blog/web/services/UserService.java b/src/main/java/com/blog/web/services/UserService.java new file mode 100644 index 0000000..8515cb1 --- /dev/null +++ b/src/main/java/com/blog/web/services/UserService.java @@ -0,0 +1,13 @@ +package com.blog.web.services; + +import com.blog.web.dto.RegistrationDto; +import com.blog.web.models.UserEntity; + + +public interface UserService { + void saveUser(RegistrationDto registrationDto); + + UserEntity findByEmail(String email); + + UserEntity findByUsername(String username); +} diff --git a/src/main/java/com/blog/web/services/impl/UserServiceImpl.java b/src/main/java/com/blog/web/services/impl/UserServiceImpl.java new file mode 100644 index 0000000..06dbc22 --- /dev/null +++ b/src/main/java/com/blog/web/services/impl/UserServiceImpl.java @@ -0,0 +1,46 @@ +package com.blog.web.services.impl; + +import com.blog.web.dto.RegistrationDto; +import com.blog.web.models.Role; +import com.blog.web.models.UserEntity; +import com.blog.web.repository.RoleRepository; +import com.blog.web.repository.UserRepository; +import com.blog.web.services.UserService; +import org.springframework.stereotype.Service; + +import java.util.Arrays; + +@Service +public class UserServiceImpl implements UserService { + private UserRepository userRepository; + private RoleRepository roleRepository; + + public UserServiceImpl(UserRepository userRepository, RoleRepository roleRepository) { + this.userRepository = userRepository; + this.roleRepository = roleRepository; + } + + @Override + public void saveUser(RegistrationDto registrationDto) { + UserEntity user = new UserEntity(); + user.setUsername(registrationDto.getUsername()); + user.setEmail(registrationDto.getEmail()); + // this is an unsafe way to store passwords in production + // it is left this way only because this is a practice project + user.setPassword(registrationDto.getPassword()); + + Role role = roleRepository.findByName("User"); + user.setRoles(Arrays.asList(role)); + userRepository.save(user); + } + + @Override + public UserEntity findByEmail(String email) { + return userRepository.findByEmail(email); + } + + @Override + public UserEntity findByUsername(String username) { + return userRepository.findByUsername(username); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 45084fa..8cc70a2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,3 +5,5 @@ spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true + +sprint.security.user.password=test diff --git a/src/main/resources/templates/auth/login.html b/src/main/resources/templates/auth/login.html new file mode 100644 index 0000000..aee1594 --- /dev/null +++ b/src/main/resources/templates/auth/login.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html lang="en" + xmlns:th="http://www.thymeleaf.org" + xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" + layout:decorate="~{layout}" +> +<body layout:fragment="content"> + +<div th:if="${param.error}" class="text-xl p-4 bg-black text-red-500">Invalid Username/Password</div> +<div th:if="${param.logout}" class="text-xl p-4 bg-black text-red-500">You have been logged out</div> + +<div class="flex justify-center bg-white p-12"> + <form th:action="@{/login}" method="post" class="w-full max-w-lg"> + <div class="flex flex-wrap -mx-3 mb-6"> + <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0"> + <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" + for="username"> + Title + </label> + <input class="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" + id="username" + type="text" + name="username" + placeholder="Ted"> + </div> + </div> + <div class="flex flex-wrap -mx-3 mb-6"> + <div class="w-full px-3"> + <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" + for="password"> + Password + </label> + <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" + id="password" + type="password" + name="password" + placeholder="Doe"> + </div> + </div> + <div class="flex flex-wrap mb-2"> + </div> + <button type="submit" value="Log in" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Log In</button> + + </form> +</div> + +</body> +</html>
\ No newline at end of file diff --git a/src/main/resources/templates/auth/register.html b/src/main/resources/templates/auth/register.html new file mode 100644 index 0000000..4f8fd44 --- /dev/null +++ b/src/main/resources/templates/auth/register.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html lang="en" + xmlns:th="http://www.thymeleaf.org" + xmlns:layout="https://www.ultraq.net.nz/thymeleaf/layout" + layout:decorate="~{layout}" +> +<body layout:fragment="content"> + +<div class="flex justify-center bg-white p-12"> + <div th:if="${param.fail}" class="text-xl p-4 bg-black text-red-500">Username or Email already exists</div> + <form th:action="@{/register/save}" th:object="${user}" method="post" class="w-full max-w-lg"> + <div class="flex flex-wrap -mx-3 mb-6"> + <div class="w-full md:w-1/2 px-3 mb-6 md:mb-0"> + <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" + for="username"> + Title + </label> + <input class="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" + id="username" + type="text" + name="username" + th:field="*{username}" + placeholder="Ted"> + <p th:if="${#fields.hasErrors('username')}" th:errors="*{username}" class="text-red-500 text-xs italic">Please fill out this field.</p> + </div> + <div class="w-full md:w-1/2 px-3"> + <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" + for="email"> + Email + </label> + <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" + id="email" + type="text" + name="email" + th:field="*{email}" + placeholder="[email protected]"> + <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="text-red-500 text-xs italic">Please fill out this field.</p> + </div> + </div> + <div class="flex flex-wrap -mx-3 mb-6"> + <div class="w-full px-3"> + <label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" + for="password"> + Password + </label> + <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" + id="password" + type="password" + name="password" + th:field="*{password}" + placeholder="Doe"> + <p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="text-red-500 text-xs italic">Please fill out this field.</p> + </div> + </div> + <div class="flex flex-wrap mb-2"> + </div> + <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Register</button> + + </form> +</div> + +</body> +</html>
\ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 1dcef48..ea12d7e 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -76,6 +76,8 @@ </div> <!--/Lead Card--> + <div th:if="${param.success}" class="text-xl p-4 bg-black text-red-500">Successful Registration!</div> + <!--Posts Container--> <div class="flex flex-wrap justify-between pt-12 -mx-6"> diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html index 17d6939..a5bdb2c 100644 --- a/src/main/resources/templates/layout.html +++ b/src/main/resources/templates/layout.html @@ -42,10 +42,10 @@ <a class="inline-block text-indigo-200 no-underline hover:text-gray-100 hover:text-underline py-2 px-2" href="/articles/new">NEW</a> </li> <li class="mr-2"> - <a class="inline-block text-indigo-200 no-underline hover:text-indigo-100 hover:text-underline py-2 px-2" href="#">LINK</a> + <a class="inline-block text-indigo-200 no-underline hover:text-indigo-100 hover:text-underline py-2 px-2" href="/register">REGISTER</a> </li> <li class="mr-2"> - <a class="inline-block text-indigo-200 no-underline hover:text-indigo-100 hover:text-underline py-2 px-2" href="#">LINK</a> + <a class="inline-block text-indigo-200 no-underline hover:text-indigo-100 hover:text-underline py-2 px-2" href="/login">LOGIN</a> </li> </ul> <form th:action="@{/articles/search}" class="w-full max-w-md"> |
