summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorrealtradam <[email protected]>2024-07-13 00:28:07 -0400
committerrealtradam <[email protected]>2024-07-13 00:28:07 -0400
commit3ea4cd2f9b3808ef645092816d888de406580e6d (patch)
tree81cc8005f1ae329bd1cbb65def26b53ff495134d
parentd0e45a9093b33d4e5cb5f57fabdcb807dc8e8ff0 (diff)
downloadspring-blog-3ea4cd2f9b3808ef645092816d888de406580e6d.tar.gz
spring-blog-3ea4cd2f9b3808ef645092816d888de406580e6d.zip
implement user registration
-rw-r--r--pom.xml4
-rw-r--r--src/main/java/com/blog/web/controllers/ArticleController.java6
-rw-r--r--src/main/java/com/blog/web/controllers/AuthController.java64
-rw-r--r--src/main/java/com/blog/web/dto/RegistrationDto.java18
-rw-r--r--src/main/java/com/blog/web/models/Role.java26
-rw-r--r--src/main/java/com/blog/web/models/UserEntity.java32
-rw-r--r--src/main/java/com/blog/web/repository/RoleRepository.java8
-rw-r--r--src/main/java/com/blog/web/repository/UserRepository.java9
-rw-r--r--src/main/java/com/blog/web/services/ArticleService.java1
-rw-r--r--src/main/java/com/blog/web/services/UserService.java13
-rw-r--r--src/main/java/com/blog/web/services/impl/UserServiceImpl.java46
-rw-r--r--src/main/resources/application.properties2
-rw-r--r--src/main/resources/templates/auth/login.html48
-rw-r--r--src/main/resources/templates/auth/register.html63
-rw-r--r--src/main/resources/templates/index.html2
-rw-r--r--src/main/resources/templates/layout.html4
16 files changed, 341 insertions, 5 deletions
diff --git a/pom.xml b/pom.xml
index 7ceafb8..d5af752 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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">