diff --git a/build.gradle b/build.gradle index 7082b804..fb35b3de 100644 --- a/build.gradle +++ b/build.gradle @@ -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') { diff --git a/src/main/java/com/tiki/server/auth/config/SecurityConfig.java b/src/main/java/com/tiki/server/auth/config/SecurityConfig.java index 6bab9b45..56d19386 100644 --- a/src/main/java/com/tiki/server/auth/config/SecurityConfig.java +++ b/src/main/java/com/tiki/server/auth/config/SecurityConfig.java @@ -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" }; diff --git a/src/main/java/com/tiki/server/auth/service/AuthService.java b/src/main/java/com/tiki/server/auth/service/AuthService.java index 2f322e40..f0911cc9 100644 --- a/src/main/java/com/tiki/server/auth/service/AuthService.java +++ b/src/main/java/com/tiki/server/auth/service/AuthService.java @@ -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; @@ -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) { diff --git a/src/main/java/com/tiki/server/common/entity/Email.java b/src/main/java/com/tiki/server/common/entity/Email.java new file mode 100644 index 00000000..4994769a --- /dev/null +++ b/src/main/java/com/tiki/server/common/entity/Email.java @@ -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); + } + } +} diff --git a/src/main/java/com/tiki/server/common/handler/ErrorHandler.java b/src/main/java/com/tiki/server/common/handler/ErrorHandler.java index cb1eafd3..07e0436f 100644 --- a/src/main/java/com/tiki/server/common/handler/ErrorHandler.java +++ b/src/main/java/com/tiki/server/common/handler/ErrorHandler.java @@ -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; @@ -67,8 +67,8 @@ public ResponseEntity externalException(ExternalException exceptio return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } - @ExceptionHandler(MailException.class) - public ResponseEntity MailException(MailException exception) { + @ExceptionHandler(EmailVerificationException.class) + public ResponseEntity MailException(EmailVerificationException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); diff --git a/src/main/java/com/tiki/server/emailverification/adapter/EmailVerificationFinder.java b/src/main/java/com/tiki/server/emailverification/adapter/EmailVerificationFinder.java new file mode 100644 index 00000000..b6ba986b --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/adapter/EmailVerificationFinder.java @@ -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)); + } +} diff --git a/src/main/java/com/tiki/server/emailverification/adapter/EmailVerificationSaver.java b/src/main/java/com/tiki/server/emailverification/adapter/EmailVerificationSaver.java new file mode 100644 index 00000000..ebf50861 --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/adapter/EmailVerificationSaver.java @@ -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); + } +} diff --git a/src/main/java/com/tiki/server/mail/config/MailConfig.java b/src/main/java/com/tiki/server/emailverification/config/EmailConfig.java similarity index 80% rename from src/main/java/com/tiki/server/mail/config/MailConfig.java rename to src/main/java/com/tiki/server/emailverification/config/EmailConfig.java index 34a9436b..3506d6a0 100644 --- a/src/main/java/com/tiki/server/mail/config/MailConfig.java +++ b/src/main/java/com/tiki/server/emailverification/config/EmailConfig.java @@ -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; @@ -10,13 +8,13 @@ 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() { @@ -24,9 +22,9 @@ public JavaMailSender setProperties() { 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); diff --git a/src/main/java/com/tiki/server/mail/constants/MailConstants.java b/src/main/java/com/tiki/server/emailverification/constants/EmailConstants.java similarity index 59% rename from src/main/java/com/tiki/server/mail/constants/MailConstants.java rename to src/main/java/com/tiki/server/emailverification/constants/EmailConstants.java index 7bbe03d0..428e8e32 100644 --- a/src/main/java/com/tiki/server/mail/constants/MailConstants.java +++ b/src/main/java/com/tiki/server/emailverification/constants/EmailConstants.java @@ -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 = "hello.wer.tiki@gmail.com"; public static final String MAIL_SUBJECT_SIGN_UP = "[Ti.Ki] 회원가입: 이메일 인증번호 안내"; @@ -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"; } diff --git a/src/main/java/com/tiki/server/mail/controller/MailController.java b/src/main/java/com/tiki/server/emailverification/controller/EmailVerificationController.java similarity index 52% rename from src/main/java/com/tiki/server/mail/controller/MailController.java rename to src/main/java/com/tiki/server/emailverification/controller/EmailVerificationController.java index 81d4af3b..12c090a0 100644 --- a/src/main/java/com/tiki/server/mail/controller/MailController.java +++ b/src/main/java/com/tiki/server/emailverification/controller/EmailVerificationController.java @@ -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; @@ -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 sendSignUpMail(@RequestBody MailRequest mailRequest) { - mailService.sendSignUp(mailRequest); + public ResponseEntity sendSignUpMail(@RequestBody EmailRequest mailRequest) { + emailVerificationService.sendSignUp(mailRequest); return ResponseEntity.created(getUri("/")).body(success(SUCCESS_SEND_EMAIL.getMessage())); } @PostMapping("/password") - public ResponseEntity sendChangingPasswordMail(@RequestBody MailRequest mailRequest) { - mailService.sendChangingPassword(mailRequest); + public ResponseEntity sendChangingPasswordMail(@RequestBody EmailRequest mailRequest) { + emailVerificationService.sendChangingPassword(mailRequest); return ResponseEntity.created(getUri("/")).body(success(SUCCESS_SEND_EMAIL.getMessage())); } @PostMapping("/checking") - public ResponseEntity checkCode(@RequestBody CodeCheck codeCheck) { - mailService.checkCode(codeCheck); + public ResponseEntity checkCode(@RequestBody CodeVerificationRequest codeVerificationRequest) { + emailVerificationService.checkCode(codeVerificationRequest); return ResponseEntity.created(getUri("/")).body(success(SUCCESS_VALIDATION.getMessage())); } - } diff --git a/src/main/java/com/tiki/server/mail/controller/docs/MailControllerDocs.java b/src/main/java/com/tiki/server/emailverification/controller/docs/EmailVerificationControllerDocs.java similarity index 86% rename from src/main/java/com/tiki/server/mail/controller/docs/MailControllerDocs.java rename to src/main/java/com/tiki/server/emailverification/controller/docs/EmailVerificationControllerDocs.java index a07bdfb5..98ee4a70 100644 --- a/src/main/java/com/tiki/server/mail/controller/docs/MailControllerDocs.java +++ b/src/main/java/com/tiki/server/emailverification/controller/docs/EmailVerificationControllerDocs.java @@ -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; @@ -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 = "회원가입 메일 전송", @@ -40,7 +39,7 @@ public interface MailControllerDocs { description = "서버 내부 오류", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))} ) - ResponseEntity sendSignUpMail(@RequestBody MailRequest mailRequest); + ResponseEntity sendSignUpMail(@RequestBody EmailRequest mailRequest); @Operation( summary = "비밀번호 재설정 메일 전송", @@ -64,7 +63,7 @@ public interface MailControllerDocs { description = "서버 내부 오류", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))} ) - ResponseEntity sendChangingPasswordMail(@RequestBody MailRequest mailRequest); + ResponseEntity sendChangingPasswordMail(@RequestBody EmailRequest mailRequest); @Operation( summary = "메일 인증", @@ -92,5 +91,5 @@ public interface MailControllerDocs { description = "서버 내부 오류", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))} ) - ResponseEntity checkCode(@RequestBody CodeCheck codeCheck); + ResponseEntity checkCode(@RequestBody CodeVerificationRequest verificationCodeRequest); } diff --git a/src/main/java/com/tiki/server/emailverification/domain/EmailVerification.java b/src/main/java/com/tiki/server/emailverification/domain/EmailVerification.java new file mode 100644 index 00000000..96240b94 --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/domain/EmailVerification.java @@ -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); + } + } +} diff --git a/src/main/java/com/tiki/server/emailverification/domain/MailSender.java b/src/main/java/com/tiki/server/emailverification/domain/MailSender.java new file mode 100644 index 00000000..b28ca585 --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/domain/MailSender.java @@ -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); + } +} diff --git a/src/main/java/com/tiki/server/emailverification/dto/request/CodeVerificationRequest.java b/src/main/java/com/tiki/server/emailverification/dto/request/CodeVerificationRequest.java new file mode 100644 index 00000000..e61d8ef3 --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/dto/request/CodeVerificationRequest.java @@ -0,0 +1,11 @@ +package com.tiki.server.emailverification.dto.request; + +import lombok.NonNull; + +public record CodeVerificationRequest( + @NonNull + String email, + @NonNull + String code +) { +} diff --git a/src/main/java/com/tiki/server/emailverification/dto/request/EmailRequest.java b/src/main/java/com/tiki/server/emailverification/dto/request/EmailRequest.java new file mode 100644 index 00000000..de3dcf14 --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/dto/request/EmailRequest.java @@ -0,0 +1,8 @@ +package com.tiki.server.emailverification.dto.request; + +import lombok.NonNull; + +public record EmailRequest( + @NonNull String email +) { +} \ No newline at end of file diff --git a/src/main/java/com/tiki/server/emailverification/exception/EmailVerificationException.java b/src/main/java/com/tiki/server/emailverification/exception/EmailVerificationException.java new file mode 100644 index 00000000..56ebfe51 --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/exception/EmailVerificationException.java @@ -0,0 +1,15 @@ +package com.tiki.server.emailverification.exception; + +import com.tiki.server.emailverification.message.ErrorCode; +import lombok.Getter; + +@Getter +public class EmailVerificationException extends RuntimeException { + + private final ErrorCode errorCode; + + public EmailVerificationException(ErrorCode errorCode) { + super("[EmailVerificationException] : " + errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/com/tiki/server/mail/message/ErrorCode.java b/src/main/java/com/tiki/server/emailverification/message/ErrorCode.java similarity index 93% rename from src/main/java/com/tiki/server/mail/message/ErrorCode.java rename to src/main/java/com/tiki/server/emailverification/message/ErrorCode.java index 6eb5096e..8a9f77b5 100644 --- a/src/main/java/com/tiki/server/mail/message/ErrorCode.java +++ b/src/main/java/com/tiki/server/emailverification/message/ErrorCode.java @@ -1,4 +1,4 @@ -package com.tiki.server.mail.message; +package com.tiki.server.emailverification.message; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/tiki/server/mail/message/SuccessMessage.java b/src/main/java/com/tiki/server/emailverification/message/SuccessMessage.java similarity index 83% rename from src/main/java/com/tiki/server/mail/message/SuccessMessage.java rename to src/main/java/com/tiki/server/emailverification/message/SuccessMessage.java index d196c1f4..cde27f14 100644 --- a/src/main/java/com/tiki/server/mail/message/SuccessMessage.java +++ b/src/main/java/com/tiki/server/emailverification/message/SuccessMessage.java @@ -1,4 +1,4 @@ -package com.tiki.server.mail.message; +package com.tiki.server.emailverification.message; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/tiki/server/emailverification/repository/EmailVerificationRepository.java b/src/main/java/com/tiki/server/emailverification/repository/EmailVerificationRepository.java new file mode 100644 index 00000000..6dcda18e --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/repository/EmailVerificationRepository.java @@ -0,0 +1,13 @@ +package com.tiki.server.emailverification.repository; + +import com.tiki.server.emailverification.domain.EmailVerification; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface EmailVerificationRepository extends CrudRepository { + + Optional findById(String email); +} diff --git a/src/main/java/com/tiki/server/emailverification/service/EmailVerificationService.java b/src/main/java/com/tiki/server/emailverification/service/EmailVerificationService.java new file mode 100644 index 00000000..bcc7a80c --- /dev/null +++ b/src/main/java/com/tiki/server/emailverification/service/EmailVerificationService.java @@ -0,0 +1,48 @@ +package com.tiki.server.emailverification.service; + +import com.tiki.server.common.entity.Email; +import com.tiki.server.emailverification.adapter.EmailVerificationFinder; +import com.tiki.server.emailverification.adapter.EmailVerificationSaver; +import com.tiki.server.emailverification.dto.request.EmailRequest; +import com.tiki.server.emailverification.dto.request.CodeVerificationRequest; +import com.tiki.server.emailverification.domain.EmailVerification; +import com.tiki.server.emailverification.domain.MailSender; +import com.tiki.server.member.adapter.MemberFinder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.tiki.server.emailverification.constants.EmailConstants.*; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EmailVerificationService { + + private final MemberFinder memberFinder; + private final EmailVerificationFinder emailVerificationFinder; + private final EmailVerificationSaver mailSaver; + private final MailSender mailSender; + + @Transactional + public void sendSignUp(EmailRequest mailRequest) { + Email email = Email.from(mailRequest.email()); + memberFinder.checkPresent(email); + mailSaver.save(mailSender.sendVerificationMail(email, MAIL_SUBJECT_SIGN_UP)); + } + + @Transactional + public void sendChangingPassword(EmailRequest mailRequest) { + Email email = Email.from(mailRequest.email()); + memberFinder.checkEmpty(email); + mailSaver.save(mailSender.sendVerificationMail(email, MAIL_SUBJECT_CHANGING_PASSWORD)); + } + + public void checkCode(CodeVerificationRequest codeVerificationRequest) { + Email email = Email.from(codeVerificationRequest.email()); + EmailVerification emailVerification = emailVerificationFinder.findById(email.getEmail()); + emailVerification.verify(codeVerificationRequest.code()); + } +} \ No newline at end of file diff --git a/src/main/java/com/tiki/server/mail/adapter/MailFinder.java b/src/main/java/com/tiki/server/mail/adapter/MailFinder.java deleted file mode 100644 index 05362966..00000000 --- a/src/main/java/com/tiki/server/mail/adapter/MailFinder.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.tiki.server.mail.adapter; - -import com.tiki.server.common.support.RepositoryAdapter; -import com.tiki.server.mail.entity.Mail; -import com.tiki.server.mail.repository.MailRepository; -import lombok.RequiredArgsConstructor; - -import java.util.Optional; - -@RepositoryAdapter -@RequiredArgsConstructor -public class MailFinder { - - private final MailRepository mailRepository; - - public Optional findById(String email) { - return mailRepository.findById(email); - } -} diff --git a/src/main/java/com/tiki/server/mail/adapter/MailSaver.java b/src/main/java/com/tiki/server/mail/adapter/MailSaver.java deleted file mode 100644 index 3ba32539..00000000 --- a/src/main/java/com/tiki/server/mail/adapter/MailSaver.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.tiki.server.mail.adapter; - -import com.tiki.server.common.support.RepositoryAdapter; -import com.tiki.server.mail.entity.Mail; -import com.tiki.server.mail.repository.MailRepository; -import lombok.RequiredArgsConstructor; - -@RepositoryAdapter -@RequiredArgsConstructor -public class MailSaver { - - private final MailRepository mailRepository; - - public void save(Mail mail) { - mailRepository.save(mail); - } -} diff --git a/src/main/java/com/tiki/server/mail/dto/request/CodeCheck.java b/src/main/java/com/tiki/server/mail/dto/request/CodeCheck.java deleted file mode 100644 index 726928a9..00000000 --- a/src/main/java/com/tiki/server/mail/dto/request/CodeCheck.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.tiki.server.mail.dto.request; - -import jakarta.validation.constraints.Email; -import lombok.NonNull; - -public record CodeCheck( - @Email - String email, - @NonNull - String code -) { -} diff --git a/src/main/java/com/tiki/server/mail/dto/request/MailRequest.java b/src/main/java/com/tiki/server/mail/dto/request/MailRequest.java deleted file mode 100644 index 949902ae..00000000 --- a/src/main/java/com/tiki/server/mail/dto/request/MailRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.tiki.server.mail.dto.request; - -import jakarta.validation.constraints.Email; -import lombok.NonNull; - -public record MailRequest( - @Email - @NonNull - String email -) { -} \ No newline at end of file diff --git a/src/main/java/com/tiki/server/mail/entity/Mail.java b/src/main/java/com/tiki/server/mail/entity/Mail.java deleted file mode 100644 index 12de877f..00000000 --- a/src/main/java/com/tiki/server/mail/entity/Mail.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.tiki.server.mail.entity; - -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 jakarta.persistence.GenerationType.IDENTITY; -import static lombok.AccessLevel.PRIVATE; - -@Getter -@AllArgsConstructor(access = PRIVATE) -@Builder -@RedisHash(value = "mailVerification", timeToLive = 180) -public class Mail { - - @Id - @GeneratedValue(strategy = IDENTITY) - private String id; - - private String code; - - public static Mail of(String email, String code) { - return Mail.builder().id(email).code(code).build(); - } -} diff --git a/src/main/java/com/tiki/server/mail/exception/MailException.java b/src/main/java/com/tiki/server/mail/exception/MailException.java deleted file mode 100644 index 27a8601c..00000000 --- a/src/main/java/com/tiki/server/mail/exception/MailException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tiki.server.mail.exception; - -import com.tiki.server.mail.message.ErrorCode; -import lombok.Getter; - -@Getter -public class MailException extends RuntimeException { - - private final ErrorCode errorCode; - - public MailException(ErrorCode errorCode) { - super("[MailException] : " + errorCode.getMessage()); - this.errorCode = errorCode; - } -} diff --git a/src/main/java/com/tiki/server/mail/repository/MailRepository.java b/src/main/java/com/tiki/server/mail/repository/MailRepository.java deleted file mode 100644 index 1df9e615..00000000 --- a/src/main/java/com/tiki/server/mail/repository/MailRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.tiki.server.mail.repository; - -import com.tiki.server.mail.entity.Mail; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface MailRepository extends CrudRepository { - - Optional findById(String email); -} diff --git a/src/main/java/com/tiki/server/mail/service/MailService.java b/src/main/java/com/tiki/server/mail/service/MailService.java deleted file mode 100644 index 9c3baa81..00000000 --- a/src/main/java/com/tiki/server/mail/service/MailService.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.tiki.server.mail.service; - -import com.tiki.server.mail.adapter.MailFinder; -import com.tiki.server.mail.adapter.MailSaver; -import com.tiki.server.mail.dto.request.CodeCheck; -import com.tiki.server.mail.dto.request.MailRequest; -import com.tiki.server.mail.entity.Mail; -import com.tiki.server.mail.exception.MailException; -import com.tiki.server.member.adapter.MemberFinder; -import com.tiki.server.member.exception.MemberException; - -import jakarta.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring6.SpringTemplateEngine; - -import java.util.Optional; -import java.util.Random; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static com.tiki.server.mail.message.ErrorCode.*; -import static com.tiki.server.member.message.ErrorCode.*; -import static com.tiki.server.mail.constants.MailConstants.*; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class MailService { - - private final SpringTemplateEngine templateEngine; - private final JavaMailSender javaMailSender; - private final MemberFinder memberFinder; - private final MailFinder mailFinder; - private final MailSaver mailSaver; - - @Transactional - public void sendSignUp(MailRequest mailRequest) { - String email = mailRequest.email(); - checkSignUpEmailFormat(email); - sendMail(email, MAIL_SUBJECT_SIGN_UP); - } - - @Transactional - public void sendChangingPassword(MailRequest mailRequest) { - String email = mailRequest.email(); - checkPasswordEmailFormat(email); - sendMail(email, MAIL_SUBJECT_CHANGING_PASSWORD); - } - - public void checkCode(CodeCheck codeCheck) { - String email = codeCheck.email(); - checkEmailFormat(email); - Optional mail = mailFinder.findById(email); - if (mail.isEmpty()) { - throw new MailException(INVALID_REQUEST); - } - if (!mail.get().getCode().equals(codeCheck.code())) { - throw new MailException(INVALID_MATCHED); - } - } - - private void checkSignUpEmailFormat(String email) { - checkMemberPresent(email); - checkEmailFormat(email); - } - - private void checkMemberPresent(String email) { - memberFinder.findByEmail(email).ifPresent(member -> { - throw new MemberException(CONFLICT_MEMBER); - }); - } - - private void checkPasswordEmailFormat(String email) { - checkMemberEmpty(email); - checkEmailFormat(email); - } - - private void checkMemberEmpty(String email) { - if (memberFinder.findByEmail(email).isEmpty()) { - throw new MemberException(INVALID_MEMBER); - } - } - - private void sendMail(String email, String subject) { - MimeMessage mail = makeMail(email, subject); - send(mail); - } - - private void checkEmailFormat(String email) { - if (!(email.endsWith(MAIL_FORMAT_EDU) || email.endsWith(MAIL_FORMAT_AC_KR))) { - throw new MemberException(INVALID_EMAIL); - } - } - - private MimeMessage makeMail(String email, String subject) { - String code = generateRandomValue(); - MimeMessage message = makeMessage(email, code, subject); - mailSaver.save(Mail.of(email, code)); - return message; - } - - private MimeMessage makeMessage(String 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); - helper.setSubject(subject); - helper.setText(setContext(code), true); - helper.addInline("image", new ClassPathResource(IMG_PATH)); - return message; - } catch (Exception e) { - throw new MailException(MESSAGE_HELPER_ERROR); - } - } - - private void send(MimeMessage email) { - javaMailSender.send(email); - } - - private static String generateRandomValue() { - Random random = new Random(); - return IntStream.range(0, 6).mapToObj(i -> String.valueOf(random.nextInt(10))).collect(Collectors.joining()); - } - - public String setContext(String code) { - Context context = new Context(); - context.setVariable("code", code); - return templateEngine.process(TEMPLATE_NAME, context); - } -} \ No newline at end of file diff --git a/src/main/java/com/tiki/server/member/adapter/MemberFinder.java b/src/main/java/com/tiki/server/member/adapter/MemberFinder.java index 6a812554..f171bff8 100644 --- a/src/main/java/com/tiki/server/member/adapter/MemberFinder.java +++ b/src/main/java/com/tiki/server/member/adapter/MemberFinder.java @@ -4,6 +4,7 @@ import static com.tiki.server.member.message.ErrorCode.INVALID_MEMBER; import com.tiki.server.common.support.RepositoryAdapter; +import com.tiki.server.common.entity.Email; import com.tiki.server.member.entity.Member; import com.tiki.server.member.exception.MemberException; import com.tiki.server.member.repository.MemberRepository; @@ -18,7 +19,7 @@ public class MemberFinder { private final MemberRepository memberRepository; - public Optional findByEmail(String email) { + public Optional findByEmail(Email email) { return memberRepository.findByEmail(email); } @@ -26,11 +27,11 @@ public Member findById(long memberId) { return memberRepository.findById(memberId).orElseThrow(() -> new MemberException(INVALID_MEMBER)); } - public Member checkEmpty(String email) { - return findByEmail(email).orElseThrow(() -> new MemberException(INVALID_MEMBER)); + public Member checkEmpty(Email email) { + return memberRepository.findByEmail(email).orElseThrow(() -> new MemberException(INVALID_MEMBER)); } - public void checkPresent(String email) { + public void checkPresent(Email email) { findByEmail(email).ifPresent((member) -> { throw new MemberException(CONFLICT_MEMBER); }); diff --git a/src/main/java/com/tiki/server/member/dto/response/BelongTeamsGetResponse.java b/src/main/java/com/tiki/server/member/dto/response/BelongTeamsGetResponse.java index eac559ae..dfe971e1 100644 --- a/src/main/java/com/tiki/server/member/dto/response/BelongTeamsGetResponse.java +++ b/src/main/java/com/tiki/server/member/dto/response/BelongTeamsGetResponse.java @@ -10,7 +10,7 @@ @Builder(access = PRIVATE) public record BelongTeamsGetResponse( - List belongTeamGetResponses + @NonNull List belongTeamGetResponses ) { public static BelongTeamsGetResponse from(List belongTeamGetResponses) { diff --git a/src/main/java/com/tiki/server/member/entity/Member.java b/src/main/java/com/tiki/server/member/entity/Member.java index c87f6dc2..b0c15650 100644 --- a/src/main/java/com/tiki/server/member/entity/Member.java +++ b/src/main/java/com/tiki/server/member/entity/Member.java @@ -1,17 +1,12 @@ package com.tiki.server.member.entity; -import static com.tiki.server.mail.constants.MailConstants.MAIL_FORMAT_AC_KR; -import static com.tiki.server.mail.constants.MailConstants.MAIL_FORMAT_EDU; -import static com.tiki.server.member.message.ErrorCode.INVALID_EMAIL; -import static com.tiki.server.member.message.ErrorCode.UNMATCHED_PASSWORD; import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.GenerationType.IDENTITY; -import com.tiki.server.member.exception.MemberException; import java.time.LocalDate; import com.tiki.server.common.entity.BaseTime; - +import com.tiki.server.common.entity.Email; import com.tiki.server.common.entity.University; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -32,7 +27,7 @@ public class Member extends BaseTime { @Column(name = "member_id") private Long id; - private String email; + private Email email; private String password; @@ -49,25 +44,16 @@ public static Member of( String name, LocalDate birth, University univ) { - val member = Member.builder() - .email(email) + return Member.builder() + .email(Email.from(email)) .password(password) .name(name) .birth(birth) .univ(univ) .build(); - - member.checkMailFormat(); - return member; } public void resetPassword(String password) { this.password = password; } - - private void checkMailFormat() { - if (!(this.email.endsWith(MAIL_FORMAT_EDU) || this.email.endsWith(MAIL_FORMAT_AC_KR))) { - throw new MemberException(INVALID_EMAIL); - } - } } diff --git a/src/main/java/com/tiki/server/member/repository/MemberRepository.java b/src/main/java/com/tiki/server/member/repository/MemberRepository.java index 1bd3d805..dffa8d4a 100644 --- a/src/main/java/com/tiki/server/member/repository/MemberRepository.java +++ b/src/main/java/com/tiki/server/member/repository/MemberRepository.java @@ -1,5 +1,6 @@ package com.tiki.server.member.repository; +import com.tiki.server.common.entity.Email; import org.springframework.data.jpa.repository.JpaRepository; import com.tiki.server.member.entity.Member; @@ -7,5 +8,5 @@ public interface MemberRepository extends JpaRepository { - Optional findByEmail(String email); + Optional findByEmail(Email email); } diff --git a/src/main/java/com/tiki/server/member/service/MemberService.java b/src/main/java/com/tiki/server/member/service/MemberService.java index db232c6b..0b25060f 100644 --- a/src/main/java/com/tiki/server/member/service/MemberService.java +++ b/src/main/java/com/tiki/server/member/service/MemberService.java @@ -5,6 +5,7 @@ import com.tiki.server.member.dto.request.PasswordChangeRequest; import com.tiki.server.member.dto.request.MemberProfileCreateRequest; import com.tiki.server.member.dto.response.BelongTeamsGetResponse; +import com.tiki.server.common.entity.Email; import com.tiki.server.member.entity.Member; import com.tiki.server.member.exception.MemberException; import com.tiki.server.memberteammanager.adapter.MemberTeamManagerFinder; @@ -28,7 +29,7 @@ public class MemberService { @Transactional public void signUp(MemberProfileCreateRequest request) { - memberFinder.checkPresent(request.email()); + memberFinder.checkPresent(Email.from(request.email())); checkPassword(request.password(), request.passwordChecker()); Member member = createMember(request); saveMember(member); @@ -36,7 +37,7 @@ public void signUp(MemberProfileCreateRequest request) { @Transactional public void changePassword(PasswordChangeRequest request) { - Member member = memberFinder.checkEmpty(request.email()); + Member member = memberFinder.checkEmpty(Email.from(request.email())); checkPassword(request.password(), request.passwordChecker()); member.resetPassword(passwordEncoder.encode(request.password())); } diff --git a/src/main/java/com/tiki/server/team/service/TeamService.java b/src/main/java/com/tiki/server/team/service/TeamService.java index cc1dec6b..0cebe1e4 100644 --- a/src/main/java/com/tiki/server/team/service/TeamService.java +++ b/src/main/java/com/tiki/server/team/service/TeamService.java @@ -3,7 +3,6 @@ import static com.tiki.server.common.entity.Position.ADMIN; import static com.tiki.server.team.message.ErrorCode.INVALID_AUTHORIZATION_DELETE; -import java.util.HashSet; import java.util.List; import com.tiki.server.document.adapter.DocumentDeleter;