Skip to content

Commit

Permalink
Merge pull request #150 from Team-Tiki/chore/#147-auth-member-mail-te…
Browse files Browse the repository at this point in the history
…am-refactor

[REFACTOR] 메일 도메인 리팩토링
  • Loading branch information
paragon0107 authored Oct 4, 2024
2 parents bfcba1f + 663e4d2 commit f960b45
Show file tree
Hide file tree
Showing 34 changed files with 321 additions and 325 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ dependencies {

//thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

//Validation
implementation 'commons-validator:commons-validator:1.7'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class SecurityConfig {
"/api/v1/auth/reissue",
"/api/v1/members/password",
"/api/v1/members",
"/api/v1/mail/**",
"/api/v1/email-verification/**",
"/actuator/health"
};

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/tiki/server/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.tiki.server.auth.token.adapter.TokenFinder;
import com.tiki.server.auth.token.adapter.TokenSaver;
import com.tiki.server.auth.token.entity.Token;
import com.tiki.server.common.entity.Email;
import com.tiki.server.member.adapter.MemberFinder;
import com.tiki.server.member.entity.Member;
import com.tiki.server.member.exception.MemberException;
Expand Down Expand Up @@ -64,7 +65,7 @@ public ReissueGetResponse reissueToken(HttpServletRequest request) {
}

private Member checkMemberEmpty(SignInRequest request) {
return memberFinder.findByEmail(request.email()).orElseThrow(() -> new MemberException(INVALID_MEMBER));
return memberFinder.findByEmail(Email.from(request.email())).orElseThrow(() -> new MemberException(INVALID_MEMBER));
}

private void checkTokenEmpty(String token) {
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/tiki/server/common/entity/Email.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.tiki.server.common.entity;

import com.tiki.server.member.exception.MemberException;
import jakarta.persistence.Embeddable;
import lombok.*;
import org.apache.commons.validator.routines.EmailValidator;

import static com.tiki.server.emailverification.constants.EmailConstants.MAIL_FORMAT_AC_KR;
import static com.tiki.server.emailverification.constants.EmailConstants.MAIL_FORMAT_EDU;
import static com.tiki.server.member.message.ErrorCode.INVALID_EMAIL;

@Getter
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Email {

private String email;
public static Email from(String email){
checkMailFormat(email);
return new Email(email);
}

private static void checkMailFormat(String email) {
if (!EmailValidator.getInstance().isValid(email) || !(email.endsWith(MAIL_FORMAT_EDU) || email.endsWith(MAIL_FORMAT_AC_KR))) {
throw new MemberException(INVALID_EMAIL);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.tiki.server.auth.exception.AuthException;
import com.tiki.server.common.dto.ErrorCodeResponse;
import com.tiki.server.mail.exception.MailException;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
Expand Down Expand Up @@ -67,8 +67,8 @@ public ResponseEntity<BaseResponse> externalException(ExternalException exceptio
return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage()));
}

@ExceptionHandler(MailException.class)
public ResponseEntity<BaseResponse> MailException(MailException exception) {
@ExceptionHandler(EmailVerificationException.class)
public ResponseEntity<BaseResponse> MailException(EmailVerificationException exception) {
log.error(exception.getMessage());
val errorCode = exception.getErrorCode();
return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.tiki.server.emailverification.adapter;

import com.tiki.server.common.support.RepositoryAdapter;
import com.tiki.server.emailverification.domain.EmailVerification;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import com.tiki.server.emailverification.repository.EmailVerificationRepository;
import lombok.RequiredArgsConstructor;

import static com.tiki.server.emailverification.message.ErrorCode.INVALID_REQUEST;

@RepositoryAdapter
@RequiredArgsConstructor
public class EmailVerificationFinder {

private final EmailVerificationRepository mailRepository;

public EmailVerification findById(String email) {
return mailRepository.findById(email).orElseThrow(() -> new EmailVerificationException(INVALID_REQUEST));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tiki.server.emailverification.adapter;

import com.tiki.server.common.support.RepositoryAdapter;
import com.tiki.server.emailverification.domain.EmailVerification;
import com.tiki.server.emailverification.repository.EmailVerificationRepository;
import lombok.RequiredArgsConstructor;

@RepositoryAdapter
@RequiredArgsConstructor
public class EmailVerificationSaver {

private final EmailVerificationRepository mailRepository;

public void save(EmailVerification mail) {
mailRepository.save(mail);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.tiki.server.mail.config;
package com.tiki.server.emailverification.config;

import com.sun.tools.jconsole.JConsoleContext;
import lombok.val;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -10,23 +8,23 @@

import java.util.Properties;

import static com.tiki.server.mail.constants.MailConstants.TIKI_EMAIL;
import static com.tiki.server.emailverification.constants.EmailConstants.TIKI_EMAIL;

@Configuration
public class MailConfig {
public class EmailConfig {

@Value("${MAIL.password}")
private String mailPassword;
private String emailPassword;

@Bean
public JavaMailSender setProperties() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername(TIKI_EMAIL);
mailSender.setPassword(mailPassword);
mailSender.setPassword(emailPassword);

val javaMailProperties = getProperties();
Properties javaMailProperties = getProperties();

mailSender.setJavaMailProperties(javaMailProperties);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tiki.server.mail.constants;
package com.tiki.server.emailverification.constants;

public class MailConstants {
public class EmailConstants {

public static final String TIKI_EMAIL = "[email protected]";
public static final String MAIL_SUBJECT_SIGN_UP = "[Ti.Ki] 회원가입: 이메일 인증번호 안내";
Expand All @@ -9,4 +9,9 @@ public class MailConstants {
public static final String MAIL_FORMAT_EDU = ".edu";
public static final String MAIL_FORMAT_AC_KR = ".ac.kr";
public static final String TEMPLATE_NAME = "certification";
public static final int INIT_NUM = 0;
public static final int CODE_LENGTH = 6;
public static final int CODE_NUM_MAX_VALUE_PER_WORD = 10;
public static final String CERTIFICATION_PAGE_LOGO_IMAGE_VAR = "image";
public static final String CERTIFICATION_PAGE_CODEE_VAR = "code";
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.tiki.server.mail.controller;
package com.tiki.server.emailverification.controller;

import com.tiki.server.common.dto.BaseResponse;

import com.tiki.server.mail.controller.docs.MailControllerDocs;
import com.tiki.server.mail.dto.request.CodeCheck;
import com.tiki.server.mail.dto.request.MailRequest;
import com.tiki.server.mail.service.MailService;
import com.tiki.server.emailverification.controller.docs.EmailVerificationControllerDocs;
import com.tiki.server.emailverification.dto.request.EmailRequest;
import com.tiki.server.emailverification.dto.request.CodeVerificationRequest;
import com.tiki.server.emailverification.service.EmailVerificationService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -15,33 +15,32 @@

import static com.tiki.server.common.dto.SuccessResponse.success;
import static com.tiki.server.common.support.UriGenerator.getUri;
import static com.tiki.server.mail.message.SuccessMessage.SUCCESS_SEND_EMAIL;
import static com.tiki.server.mail.message.SuccessMessage.SUCCESS_VALIDATION;
import static com.tiki.server.emailverification.message.SuccessMessage.SUCCESS_SEND_EMAIL;
import static com.tiki.server.emailverification.message.SuccessMessage.SUCCESS_VALIDATION;


@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/mail")
public class MailController implements MailControllerDocs {
@RequestMapping("/api/v1/email-verification")
public class EmailVerificationController implements EmailVerificationControllerDocs {

private final MailService mailService;
private final EmailVerificationService emailVerificationService;

@PostMapping("/signup")
public ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody MailRequest mailRequest) {
mailService.sendSignUp(mailRequest);
public ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody EmailRequest mailRequest) {
emailVerificationService.sendSignUp(mailRequest);
return ResponseEntity.created(getUri("/")).body(success(SUCCESS_SEND_EMAIL.getMessage()));
}

@PostMapping("/password")
public ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody MailRequest mailRequest) {
mailService.sendChangingPassword(mailRequest);
public ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody EmailRequest mailRequest) {
emailVerificationService.sendChangingPassword(mailRequest);
return ResponseEntity.created(getUri("/")).body(success(SUCCESS_SEND_EMAIL.getMessage()));
}

@PostMapping("/checking")
public ResponseEntity<BaseResponse> checkCode(@RequestBody CodeCheck codeCheck) {
mailService.checkCode(codeCheck);
public ResponseEntity<BaseResponse> checkCode(@RequestBody CodeVerificationRequest codeVerificationRequest) {
emailVerificationService.checkCode(codeVerificationRequest);
return ResponseEntity.created(getUri("/")).body(success(SUCCESS_VALIDATION.getMessage()));
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.tiki.server.mail.controller.docs;
package com.tiki.server.emailverification.controller.docs;

import com.tiki.server.common.dto.BaseResponse;
import com.tiki.server.common.dto.ErrorResponse;
import com.tiki.server.common.dto.SuccessResponse;
import com.tiki.server.mail.dto.request.CodeCheck;
import com.tiki.server.mail.dto.request.MailRequest;
import com.tiki.server.emailverification.dto.request.EmailRequest;

import com.tiki.server.emailverification.dto.request.CodeVerificationRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
Expand All @@ -15,8 +14,8 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;

@Tag(name = "mail", description = "메일 인증 API")
public interface MailControllerDocs {
@Tag(name = "EmailVerification", description = "메일 인증 API")
public interface EmailVerificationControllerDocs {

@Operation(
summary = "회원가입 메일 전송",
Expand All @@ -40,7 +39,7 @@ public interface MailControllerDocs {
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody MailRequest mailRequest);
ResponseEntity<BaseResponse> sendSignUpMail(@RequestBody EmailRequest mailRequest);

@Operation(
summary = "비밀번호 재설정 메일 전송",
Expand All @@ -64,7 +63,7 @@ public interface MailControllerDocs {
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody MailRequest mailRequest);
ResponseEntity<BaseResponse> sendChangingPasswordMail(@RequestBody EmailRequest mailRequest);

@Operation(
summary = "메일 인증",
Expand Down Expand Up @@ -92,5 +91,5 @@ public interface MailControllerDocs {
description = "서버 내부 오류",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))}
)
ResponseEntity<BaseResponse> checkCode(@RequestBody CodeCheck codeCheck);
ResponseEntity<BaseResponse> checkCode(@RequestBody CodeVerificationRequest verificationCodeRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.tiki.server.emailverification.domain;

import com.tiki.server.common.entity.Email;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.redis.core.RedisHash;

import static com.tiki.server.emailverification.message.ErrorCode.INVALID_MATCHED;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PRIVATE;

@Getter
@AllArgsConstructor(access = PRIVATE)
@Builder
@RedisHash(value = "mailVerification", timeToLive = 180)
public class EmailVerification {

@Id
@GeneratedValue(strategy = IDENTITY)
private String id;

private String code;

public static EmailVerification of(Email email, String code) {
return EmailVerification.builder().id(email.getEmail()).code(code).build();
}

public void verify(String code){
if(!this.code.equals(code)){
throw new EmailVerificationException(INVALID_MATCHED);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.tiki.server.emailverification.domain;

import com.tiki.server.common.entity.Email;
import com.tiki.server.emailverification.exception.EmailVerificationException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;

import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.tiki.server.emailverification.constants.EmailConstants.*;
import static com.tiki.server.emailverification.message.ErrorCode.MESSAGE_HELPER_ERROR;

@Component
@RequiredArgsConstructor
public class MailSender {

private final SpringTemplateEngine templateEngine;
private final JavaMailSender javaMailSender;

public EmailVerification sendVerificationMail(Email email, String subject) {
String code = generateRandomValue();
MimeMessage message = makeMessage(email, code, subject);
javaMailSender.send(message);
return EmailVerification.of(email, code);
}

private static String generateRandomValue() {
Random random = new Random();
return IntStream.range(INIT_NUM, CODE_LENGTH).mapToObj(i -> String.valueOf(random.nextInt(CODE_NUM_MAX_VALUE_PER_WORD))).collect(Collectors.joining());
}

private MimeMessage makeMessage(Email email, String code, String subject) {
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
helper.setFrom(TIKI_EMAIL);
helper.setTo(email.getEmail());
helper.setSubject(subject);
helper.setText(setContext(code), true);
helper.addInline(CERTIFICATION_PAGE_LOGO_IMAGE_VAR, new ClassPathResource(IMG_PATH));
return message;
} catch (Exception e) {
throw new EmailVerificationException(MESSAGE_HELPER_ERROR);
}
}

private String setContext(String code) {
Context context = new Context();
context.setVariable(CERTIFICATION_PAGE_CODEE_VAR, code);
return templateEngine.process(TEMPLATE_NAME, context);
}
}
Loading

0 comments on commit f960b45

Please sign in to comment.