본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
공부 시작 시각 인증
공부 시작 시각 인증
수강 인증 사진
수강 인증 사진
EmailVerificationRepository.java
package org.fastcampus.auth.application.Interfaces;
import org.fastcampus.auth.domain.Email;
public interface EmailVerificationRepository {
void createEmailVerification(Email email, String token);
void verifyEmail(Email email, String token);
boolean isEmailVerified(Email email);
}
UserAuthRepository.java
package org.fastcampus.auth.application.Interfaces;
import org.fastcampus.auth.domain.UserAuth;
import org.fastcampus.user.domain.User;
public interface UserAuthRepository {
UserAuth registerUser(UserAuth auth, User user);
}
AuthService.java
package org.fastcampus.auth.application;
import lombok.RequiredArgsConstructor;
import org.fastcampus.auth.application.Interfaces.EmailVerificationRepository;
import org.fastcampus.auth.application.Interfaces.UserAuthRepository;
import org.fastcampus.auth.application.dto.CreateUserAuthRequestDto;
import org.fastcampus.auth.domain.Email;
import org.fastcampus.auth.domain.UserAuth;
import org.fastcampus.user.domain.User;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserAuthRepository userAuthRepository;
private final EmailVerificationRepository verificationRepository;
public Long registerUser(CreateUserAuthRequestDto dto) {
Email email = Email.createEmail(dto.email());
if (!verificationRepository.isEmailVerified(email)) {
throw new IllegalStateException("Email is not verified");
}
UserAuth userAuth = new UserAuth(dto.email(), dto.password(), dto.role());
User user = new User(dto.name(), dto.profileUrl());
userAuth = userAuthRepository.registerUser(userAuth, user);
return userAuth.getUserId();
}
}
EmailService.java
package org.fastcampus.auth.application;
import lombok.RequiredArgsConstructor;
import org.fastcampus.auth.application.Interfaces.*;
import org.fastcampus.auth.application.dto.SendEmailRequestDto;
import org.fastcampus.auth.domain.*;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class EmailService {
private final EmailSendRepository emailSendRepository;
private final EmailVerificationRepository emailVerificationRepository;
public void sendEmail(SendEmailRequestDto dto) {
Email email = Email.createEmail(dto.email());
String token = RandomTokenGenerator.generateToken();
emailSendRepository.sendEmail(email, token);
emailVerificationRepository.createEmailVerification(email, token);
}
public void verifyEmail(String email, String token) {
Email emailValue = Email.createEmail(email);
emailVerificationRepository.verifyEmail(emailValue, token);
}
}
EmailVerificationEntity.java
package org.fastcampus.auth.repository.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.fastcampus.common.repository.entity.*;
@Entity
@Table(name = "community_email_verification")
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class EmailVerificationEntity extends TimeBaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String token;
private boolean isVerified;
public EmailVerificationEntity(String email, String token) {
this.email = email;
this.token = token;
this.isVerified = false;
}
public void updateToken(String token) {
this.token = token;
}
public boolean isVerified() {
return isVerified;
}
public void verify() {
this.isVerified = true;
}
public boolean hasSameToken(String token) {
return this.token.equals(token);
}
}
UserAuthEntity.java
package org.fastcampus.auth.repository.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.fastcampus.auth.domain.UserAuth;
import org.fastcampus.common.repository.entity.*;
@Entity
@Table(name = "community_user_auth")
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class UserAuthEntity extends TimeBaseEntity {
@Id
private String email;
private String password;
private String role;
private Long userId;
public UserAuthEntity(UserAuth userAuth, Long userId) {
this.email = userAuth.getEmail();
this.password = userAuth.getPassword();
this.role = userAuth.getUserRole();
this.userId = userId;
}
public UserAuth toUserAuth() {
return new UserAuth(email, password, role, userId);
}
}
EmailVerificationRepositoryImpl.java
package org.fastcampus.auth.repository;
import jakarta.transaction.Transactional;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.fastcampus.auth.application.Interfaces.EmailVerificationRepository;
import org.fastcampus.auth.domain.Email;
import org.fastcampus.auth.repository.entity.EmailVerificationEntity;
import org.fastcampus.auth.repository.jpa.JpaEmailVerificationRepository;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class EmailVerificationRepositoryImpl implements EmailVerificationRepository {
private final JpaEmailVerificationRepository jpaEmailVerificationRepository;
@Override
@Transactional
public void createEmailVerification(Email email, String token) {
String emailAddress = email.getEmailText();
Optional<EmailVerificationEntity> entity = jpaEmailVerificationRepository.findByEmail(emailAddress);
if (entity.isPresent()) {
EmailVerificationEntity emailVerificationEntity = entity.get();
if (emailVerificationEntity.isVerified()) {
throw new IllegalArgumentException("Email already verified");
}
emailVerificationEntity.updateToken(token);
return;
}
EmailVerificationEntity emailVerificationEntity = new EmailVerificationEntity(emailAddress, token);
jpaEmailVerificationRepository.save(emailVerificationEntity);
}
@Override
@Transactional
public void verifyEmail(Email email, String token) {
String emailAddress = email.getEmailText();
EmailVerificationEntity entity = jpaEmailVerificationRepository.findByEmail(emailAddress)
.orElseThrow(() -> new IllegalArgumentException("Email not found"));
if (entity.isVerified()) {
throw new IllegalArgumentException("Email already verified");
}
if (!entity.hasSameToken(token)) {
throw new IllegalArgumentException("Invalid token");
}
entity.verify();
}
@Override
public boolean isEmailVerified(Email email) {
EmailVerificationEntity entity = jpaEmailVerificationRepository.findByEmail(email.getEmailText())
.orElseThrow(() -> new IllegalArgumentException("Email not found"));
return entity.isVerified();
}
}
UserAuthRepository.Impl.java
package org.fastcampus.auth.repository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.fastcampus.auth.application.Interfaces.UserAuthRepository;
import org.fastcampus.auth.domain.UserAuth;
import org.fastcampus.auth.repository.entity.UserAuthEntity;
import org.fastcampus.auth.repository.jpa.JpaUserAuthRepository;
import org.fastcampus.user.application.Interfaces.UserRepository;
import org.fastcampus.user.domain.User;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class UserAuthRepositoryImpl implements UserAuthRepository {
private final JpaUserAuthRepository jpaUserAuthRepository;
private final UserRepository userRepository;
@Override
@Transactional
public UserAuth registerUser(UserAuth auth, User user) {
User savedUser = userRepository.save(user);
UserAuthEntity userAuthEntity = new UserAuthEntity(auth, savedUser.getId());
userAuthEntity = jpaUserAuthRepository.save(userAuthEntity);
return userAuthEntity.toUserAuth();
}
}
User.java
package org.fastcampus.user.domain;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.fastcampus.common.domain.*;
@Builder
@Getter
@AllArgsConstructor
public class User {
private Long id;
private UserInfo info;
private PositiveIntegerCounter followingCount;
private PositiveIntegerCounter followerCount;
public User(Long id, UserInfo userInfo) {
if (userInfo == null) {
throw new IllegalArgumentException();
}
this.id = id;
this.info = userInfo;
this.followingCount = new PositiveIntegerCounter();
this.followerCount = new PositiveIntegerCounter();
}
public User(String name, String profileUrl) {
this(null, new UserInfo(name, profileUrl));
}
public void follow(User targetUser) {
if (targetUser.equals(this)) {
throw new IllegalArgumentException();
}
followingCount.increase();
targetUser.increaseFollowerCount();
}
public void unfollow(User targetUser) {
if (targetUser.equals(this)) {
throw new IllegalArgumentException();
}
followingCount.decrease();
targetUser.decreaseFollowerCount();
}
private void increaseFollowerCount() {
followerCount.increase();
}
private void decreaseFollowerCount() {
followerCount.decrease();
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
public String getName() {
return info.getName();
}
public String getProfileUrl() {
return info.getProfileImageUrl();
}
public int followerCount() {
return followerCount.getCount();
}
public int followingCount() {
return followingCount.getCount();
}
}
SignUpController.java
package org.fastcampus.auth.ui;
import lombok.RequiredArgsConstructor;
import org.fastcampus.auth.application.*;
import org.fastcampus.auth.application.dto.*;
import org.fastcampus.common.ui.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/signup")
@RequiredArgsConstructor
public class SignUpController {
private final EmailService emailService;
private final AuthService authService;
@PostMapping("/send-verification-email")
public Response<Void> sendEmail(@RequestBody SendEmailRequestDto dto) {
emailService.sendEmail(dto);
return Response.OK(null);
}
@GetMapping("/verify-token")
public Response<Void> verifyEmail(String email, String token) {
emailService.verifyEmail(email, token);
return Response.OK(null);
}
@PostMapping("/register")
public Response<Long> register(@RequestBody CreateUserAuthRequestDto dto) {
return Response.OK(authService.registerUser(dto));
}
}
DataLoader.java
package org.fastcampus.acceptance.utils;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.fastcampus.user.application.dto.*;
import org.springframework.stereotype.Component;
import static org.fastcampus.acceptance.steps.UserAcceptanceSteps.*;
@Component
public class DataLoader {
@PersistenceContext
private EntityManager entityManager;
public void loadData() {
CreateUserRequestDto dto = new CreateUserRequestDto("test user", "");
createUser(dto);
createUser(dto);
createUser(dto);
followUser(new FollowUserRequestDto(1L, 2L));
followUser(new FollowUserRequestDto(1L, 3L));
}
public String getEmailToken(String email) {
return entityManager.createNativeQuery("SELECT token FROM community_email_verification WHERE email = ?", String.class)
.setParameter(1, email)
.getSingleResult()
.toString();
}
public boolean isEmailVerified(String email) {
return entityManager.createQuery("SELECT isVerified FROM EmailVerificationEntity WHERE email = :email", Boolean.class)
.setParameter("email", email)
.getSingleResult();
}
public Long getUserId(String email) {
return entityManager.createQuery("SELECT userId FROM UserAuthEntity WHERE email = :email", Long.class)
.setParameter("email", email)
.getSingleResult();
}
}
DatabaseCleanUp.java
package org.fastcampus.acceptance.utils;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Table;
import jakarta.transaction.Transactional;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Profile("test")
@Component
@Slf4j
public class DatabaseCleanUp implements InitializingBean {
@PersistenceContext
private EntityManager entityManager;
private List<String> tableNames;
private List<String> notGeneratedIdEntityTableNames;
@Override
public void afterPropertiesSet() {
tableNames = entityManager.getMetamodel().getEntities()
.stream()
.filter(entity -> entity.getJavaType().getAnnotation(Entity.class) != null)
.map(entity -> entity.getJavaType().getAnnotation(Table.class).name())
.toList();
notGeneratedIdEntityTableNames = List.of("community_user_relation", "community_like", "community_user_auth");
}
@Transactional
public void execute() {
entityManager.flush();
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
for (String tableName: tableNames) {
entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate();
if (!notGeneratedIdEntityTableNames.contains(tableName)) {
entityManager.createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN ID RESTART WITH 1").executeUpdate();
}
}
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
}
}
AcceptanceTestTemplate.java
package org.fastcampus.acceptance.utils;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.ActiveProfiles;
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class AcceptanceTestTemplate {
@Autowired
private DatabaseCleanUp cleanUp;
@Autowired
private DataLoader loader;
@BeforeEach
public void init() {
cleanUp.execute();
loader.loadData();
}
protected void cleanUp() {
cleanUp.execute();
}
protected String getEmailToken(String email) {
return loader.getEmailToken(email);
}
protected boolean isEmailVerified(String email) {
return loader.isEmailVerified(email);
}
protected Long getUserId(String email) {
return loader.getUserId(email);
}
}
SignUpAcceptanceSteps.java
package org.fastcampus.acceptance.steps;
import io.restassured.RestAssured;
import org.fastcampus.auth.application.dto.*;
import org.springframework.http.MediaType;
public class SignUpAcceptanceSteps {
public static Integer requestSendEmail(SendEmailRequestDto dto) {
return RestAssured
.given()
.body(dto)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.when()
.post("/signup/send-verification-email")
.then()
.extract()
.jsonPath().get("code");
}
public static Integer requestVerifyEmail(String email, String token) {
return RestAssured
.given()
.queryParam("email", email)
.queryParam("token", token)
.when()
.get("/signup/verify-token")
.then()
.extract()
.jsonPath().get("code");
}
public static Integer registerUser(CreateUserAuthRequestDto dto) {
return RestAssured
.given()
.body(dto)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.when()
.post("/signup/register")
.then()
.extract()
.jsonPath().get("code");
}
}
SignUpAcceptanceTest.java
package org.fastcampus.acceptance.auth;
import org.fastcampus.acceptance.utils.*;
import org.fastcampus.auth.application.dto.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.fastcampus.acceptance.steps.SignUpAcceptanceSteps.*;
import static org.junit.jupiter.api.Assertions.*;
class SignUpAcceptanceTest extends AcceptanceTestTemplate {
private final String email = "email@email.com";
@BeforeEach
void setUp() {
this.cleanUp();
}
@Test
void givenEmail_whenSendEmail_thenVerificationTokenSaved() {
// given
SendEmailRequestDto dto = new SendEmailRequestDto(email);
// when
Integer code = requestSendEmail(dto);
// then
String token = this.getEmailToken(email);
assertNotNull(token);
assertEquals(200, code);
}
@Test
void givenInvalidEmail_whenSendEmail_thenVerificationTokenNotSaved() {
// given
SendEmailRequestDto dto = new SendEmailRequestDto("invalid-email");
// when
Integer code = requestSendEmail(dto);
// then
assertEquals(400, code);
}
@Test
void givenSendEmail_whenVerifyEmail_thenEmailVerified() {
// given
requestSendEmail(new SendEmailRequestDto(email));
// when
String token = this.getEmailToken(email);
Integer code = requestVerifyEmail(email, token);
// then
boolean isEmailVerified = isEmailVerified(email);
assertEquals(200, code);
assertTrue(isEmailVerified);
}
@Test
void givenSendEmail_whenVerifyEmailWithInvalidToken_thenEmailNotVerified() {
// given
requestSendEmail(new SendEmailRequestDto(email));
// when
Integer code = requestVerifyEmail(email, "invalid-token");
// then
boolean isEmailVerified = isEmailVerified(email);
assertEquals(400, code);
assertFalse(isEmailVerified);
}
@Test
void givenSendVerifiedEmail_whenVerifyAgain_thenThrowError() {
// given
requestSendEmail(new SendEmailRequestDto(email));
String token = this.getEmailToken(email);
requestVerifyEmail(email, token);
// when
Integer code = requestVerifyEmail(email, token);
// then
assertEquals(400, code);
}
@Test
void givenSendEmail_whenVerifyEmailWithWrongEmail_thenThrowError() {
// given
requestSendEmail(new SendEmailRequestDto(email));
// when
Integer code = requestVerifyEmail("wrong-email", this.getEmailToken(email));
// then
assertEquals(400, code);
}
@Test
void givenVerifiedEmail_whenRegister_thenUserRegistered() {
// given
requestSendEmail(new SendEmailRequestDto(email));
String token = this.getEmailToken(email);
requestVerifyEmail(email, token);
// when
CreateUserAuthRequestDto dto = new CreateUserAuthRequestDto(email, "password", "name", "USER", "profileUrl");
Integer code = registerUser(dto);
// then
assertEquals(200, code);
Long userId = getUserId(email);
assertEquals(1L, userId);
}
@Test
void givenUnverifiedEmail_whenRegister_thenThrowError() {
// given
requestSendEmail(new SendEmailRequestDto(email));
// when
CreateUserAuthRequestDto dto = new CreateUserAuthRequestDto(email, "password", "name", "USER", "profileUrl");
Integer code = registerUser(dto);
// then
assertEquals(400, code);
}
}
학습 인증샷
학습 인증샷
공부 종료 시각 인증
공부 종료 시각 인증
https://bit.ly/4hTSJNB