From d08c36660d149f02e34e538dbc1d920522f5c86f Mon Sep 17 00:00:00 2001 From: chanho Date: Thu, 24 Aug 2023 09:32:29 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[UPDATE]=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?-=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85,=20=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=B4=EB=82=B4=EA=B8=B0=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../coding/common/constant/EmailConstant.java | 38 +++++++++++++++ .../common/constant/GlobalConstant.java | 9 ++++ .../common/security/SecurityConfig.java | 3 +- .../java/io/oopy/coding/common/util/Time.java | 11 +++++ .../email/entity/EmailCertification.java | 47 +++++++++++++++++++ .../EmailCertificationRepository.java | 11 +++++ .../email/controller/EmailController.java | 30 ++++++++++++ .../email/controller/dto/EmailRequest.java | 22 +++++++++ .../email/controller/dto/EmailResponse.java | 4 ++ .../coding/email/service/EmailService.java | 46 ++++++++++++++++++ .../coding/email/service/dto/EmailSend.java | 23 +++++++++ src/main/resources/application.yml | 11 +++++ 13 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/oopy/coding/common/constant/EmailConstant.java create mode 100644 src/main/java/io/oopy/coding/common/constant/GlobalConstant.java create mode 100644 src/main/java/io/oopy/coding/common/util/Time.java create mode 100644 src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java create mode 100644 src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java create mode 100644 src/main/java/io/oopy/coding/email/controller/EmailController.java create mode 100644 src/main/java/io/oopy/coding/email/controller/dto/EmailRequest.java create mode 100644 src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java create mode 100644 src/main/java/io/oopy/coding/email/service/EmailService.java create mode 100644 src/main/java/io/oopy/coding/email/service/dto/EmailSend.java diff --git a/build.gradle b/build.gradle index d1c935d..da70854 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'com.jcraft:jsch:0.1.55' + implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' implementation 'org.springframework.boot:spring-boot-starter-validation' diff --git a/src/main/java/io/oopy/coding/common/constant/EmailConstant.java b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java new file mode 100644 index 0000000..d11c2c4 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java @@ -0,0 +1,38 @@ +package io.oopy.coding.common.constant; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EmailConstant { + public static final Integer EMAIL_EXPIRED_PERIOD_SECONDS = 600; // 임시 10분 + public static final String TITLE = String.format("[%s] 조직 이메일 인증", GlobalConstant.PROJECT_NAME); + public static final String CONTENT = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "링크 버튼\n" + + "\n" + + "\n" + + ""; +} diff --git a/src/main/java/io/oopy/coding/common/constant/GlobalConstant.java b/src/main/java/io/oopy/coding/common/constant/GlobalConstant.java new file mode 100644 index 0000000..15a2ee9 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/constant/GlobalConstant.java @@ -0,0 +1,9 @@ +package io.oopy.coding.common.constant; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class GlobalConstant { + public static final String PROJECT_NAME = "팔만코딩경"; +} diff --git a/src/main/java/io/oopy/coding/common/security/SecurityConfig.java b/src/main/java/io/oopy/coding/common/security/SecurityConfig.java index 3d0cbc7..8d6ba80 100644 --- a/src/main/java/io/oopy/coding/common/security/SecurityConfig.java +++ b/src/main/java/io/oopy/coding/common/security/SecurityConfig.java @@ -19,7 +19,8 @@ public class SecurityConfig { "/favicon.ico", "/api-docs/**", "/test/**", - "/v3/api-docs/**", "/swagger-ui/**", "/swagger" + "/v3/api-docs/**", "/swagger-ui/**", "/swagger", + "/email/**" // todo - 삭제 }; @Bean diff --git a/src/main/java/io/oopy/coding/common/util/Time.java b/src/main/java/io/oopy/coding/common/util/Time.java new file mode 100644 index 0000000..ddd526b --- /dev/null +++ b/src/main/java/io/oopy/coding/common/util/Time.java @@ -0,0 +1,11 @@ +package io.oopy.coding.common.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Time { + public static int getNowUnixTime() { + return (int) System.currentTimeMillis() / 1000; + } +} diff --git a/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java b/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java new file mode 100644 index 0000000..09052b4 --- /dev/null +++ b/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java @@ -0,0 +1,47 @@ +package io.oopy.coding.domain.email.entity; + +import io.oopy.coding.common.constant.EmailConstant; +import io.oopy.coding.domain.entity.Auditable; +import jakarta.persistence.*; +import lombok.*; + +import static io.oopy.coding.common.util.Time.getNowUnixTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +@Table(name = "EMAIL_CERTIFICATION") +public class EmailCertification extends Auditable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column + private String email; + + @Column + private String githubId; + + @Column + private Integer expiredAt; // unix time + + @Column + private Boolean verify; + + public boolean isExpired() { + return getNowUnixTime() > expiredAt; + } + + public static EmailCertification of(String email, String githubId) { + Integer expireTime = getNowUnixTime() + EmailConstant.EMAIL_EXPIRED_PERIOD_SECONDS; + return EmailCertification.builder() + .email(email) + .githubId(githubId) + .expiredAt(expireTime) + .verify(Boolean.FALSE) + .build(); + } +} diff --git a/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java b/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java new file mode 100644 index 0000000..ebfaa93 --- /dev/null +++ b/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java @@ -0,0 +1,11 @@ +package io.oopy.coding.domain.email.repository; + +import io.oopy.coding.domain.email.entity.EmailCertification; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface EmailCertificationRepository extends JpaRepository { + + Optional findFirstByGithubIdOrderByExpiredAtDesc(String githubId); +} diff --git a/src/main/java/io/oopy/coding/email/controller/EmailController.java b/src/main/java/io/oopy/coding/email/controller/EmailController.java new file mode 100644 index 0000000..7bc00d1 --- /dev/null +++ b/src/main/java/io/oopy/coding/email/controller/EmailController.java @@ -0,0 +1,30 @@ +package io.oopy.coding.email.controller; + +import io.oopy.coding.email.controller.dto.EmailRequest; +import io.oopy.coding.email.service.EmailService; +import jakarta.mail.MessagingException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +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("/email") +@RequiredArgsConstructor +@Slf4j +public class EmailController { + + private final EmailService emailService; + + @PostMapping("/send-cert") + public ResponseEntity emailCertificate(@RequestBody EmailRequest.Send emailSend + //todo - ,@AuthenticationPrincipal UserDetails userDetails) + )throws MessagingException { + String githubId = "12345678"; + emailService.sendCertMail(emailSend.toEmailSend(githubId)); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/io/oopy/coding/email/controller/dto/EmailRequest.java b/src/main/java/io/oopy/coding/email/controller/dto/EmailRequest.java new file mode 100644 index 0000000..c21423b --- /dev/null +++ b/src/main/java/io/oopy/coding/email/controller/dto/EmailRequest.java @@ -0,0 +1,22 @@ +package io.oopy.coding.email.controller.dto; + +import io.oopy.coding.common.constant.EmailConstant; +import io.oopy.coding.email.service.dto.EmailSend; +import lombok.*; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EmailRequest { + + @Getter + public static class Send { + private String targetEmail; + + public EmailSend toEmailSend(String githubId) { + return EmailSend.of(this.targetEmail, + githubId, + EmailConstant.TITLE, + EmailConstant.CONTENT + ); + } + } +} diff --git a/src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java b/src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java new file mode 100644 index 0000000..f8b63e8 --- /dev/null +++ b/src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java @@ -0,0 +1,4 @@ +package io.oopy.coding.email.controller.dto; + +public class EmailResponse { +} diff --git a/src/main/java/io/oopy/coding/email/service/EmailService.java b/src/main/java/io/oopy/coding/email/service/EmailService.java new file mode 100644 index 0000000..f9bc35b --- /dev/null +++ b/src/main/java/io/oopy/coding/email/service/EmailService.java @@ -0,0 +1,46 @@ +package io.oopy.coding.email.service; + +import io.oopy.coding.domain.email.entity.EmailCertification; +import io.oopy.coding.domain.email.repository.EmailCertificationRepository; +import io.oopy.coding.email.service.dto.EmailSend; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class EmailService{ + + private final JavaMailSender javaMailSender; + private final EmailCertificationRepository emailCertificationRepository; + + @Transactional + public void sendCertMail(EmailSend emailSend) throws MessagingException { + checkSendValid(emailSend); + EmailCertification emailCertification = + EmailCertification.of(emailSend.getTargetEmail(), emailSend.getGithubId()); + // persist first + emailCertificationRepository.save(emailCertification); + sendMail(emailSend); + } + + private void sendMail(EmailSend emailSend) throws MessagingException { + MimeMessage message = javaMailSender.createMimeMessage(); + message.addRecipients(Message.RecipientType.TO, emailSend.getTargetEmail()); + message.setSubject(emailSend.getTitle()); + message.setText(emailSend.getContent(), "utf-8", "html"); + javaMailSender.send(message); + } + + private void checkSendValid(EmailSend emailSend) { + Optional email = emailCertificationRepository.findFirstByGithubIdOrderByExpiredAtDesc(emailSend.getGithubId()); + if (email.isPresent() && !email.get().isExpired()) + throw new RuntimeException("인증 진행중"); + } +} diff --git a/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java b/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java new file mode 100644 index 0000000..e072b6b --- /dev/null +++ b/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java @@ -0,0 +1,23 @@ +package io.oopy.coding.email.service.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class EmailSend { + + private String targetEmail; + private String githubId; + private String title; + private String content; + + public static EmailSend of(String targetEmail, String githubId, String title, String content) { + return new EmailSend(targetEmail, + githubId, + title, + content + ); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d1f160f..254caf1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -32,6 +32,17 @@ spring: max-lifetime: 1200000 # 20m (default 30m) connection-timeout: 3000 # 3s (default 30s) validation-timeout: 2000 # 2s (default 5s) + mail: + host: ${MAIL_HOST} + port: ${MAIL_PORT} + username: ${MAIL_USER_NAME} + password: ${MAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true springdoc: default-consumes-media-type: application/json;charset=UTF-8 From e04cd815e678bca868ed6ffb3b824009200bae33 Mon Sep 17 00:00:00 2001 From: chanho Date: Sun, 3 Sep 2023 13:47:21 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[UPDATE]=20jwt=20ignore=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oopy/coding/common/config/SecurityConfig.java | 3 ++- .../coding/common/jwt/JwtAuthorizationFilter.java | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/oopy/coding/common/config/SecurityConfig.java b/src/main/java/io/oopy/coding/common/config/SecurityConfig.java index ea3892f..3d17af3 100644 --- a/src/main/java/io/oopy/coding/common/config/SecurityConfig.java +++ b/src/main/java/io/oopy/coding/common/config/SecurityConfig.java @@ -29,7 +29,8 @@ public class SecurityConfig { "/api-docs/**", "/test/**", "/v3/api-docs/**", "/swagger-ui/**", "/swagger", - "/api/v1/users/login", "/api/v1/users/refresh" + "/api/v1/users/login", "/api/v1/users/refresh", + "/email/**" }; private final UserDetailServiceImpl userDetailServiceImpl; diff --git a/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java b/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java index 36b5ac5..423acf9 100644 --- a/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java +++ b/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java @@ -27,6 +27,8 @@ import java.io.IOException; import java.util.Date; import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; import static io.oopy.coding.common.jwt.AuthConstants.*; @@ -46,7 +48,10 @@ public class JwtAuthorizationFilter extends OncePerRequestFilter { private final List jwtIgnoreUrls = List.of( "/test", "/api/v1/users/login", - "/api/v1/users/refresh" + "/api/v1/users/refresh", + "/v3/api-docs/**", "/swagger-ui/**", "/swagger", + "/favicon.ico", + "/email/**" ); @Override @@ -67,7 +72,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse private boolean shouldIgnoreRequest(HttpServletRequest request) { String uri = request.getRequestURI(); String method = request.getMethod(); - return jwtIgnoreUrls.contains(uri) || "OPTIONS".equals(method); + Optional judge = jwtIgnoreUrls.stream() + .filter(v -> + Pattern.matches(v.replace("**", ".*"), uri) || + Pattern.matches(v.replace("/**", ""), uri) + ) + .findFirst(); + return !judge.isEmpty() || "OPTIONS".equals(method); } private String resolveAccessToken(HttpServletRequest request, HttpServletResponse response) throws ServletException { From 3c6fed115dc7760cc93fe79ba092f39c7e618e93 Mon Sep 17 00:00:00 2001 From: chanho Date: Sun, 3 Sep 2023 13:47:35 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[UPDATE]=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20-=20=EB=A0=88=EB=94=94=EC=8A=A4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/getContentDetailService.java | 2 +- .../coding/domain/comment/dto/CommentDTO.java | 2 +- .../coding/domain/comment/entity/Comment.java | 3 +- .../coding/domain/content/dto/ContentDTO.java | 2 +- .../domain/content/dto/ContentDetailDTO.java | 2 +- .../domain/content/entity/Category.java | 2 +- .../coding/domain/content/entity/Content.java | 4 +- .../content/entity/ContentCategory.java | 2 +- .../content/repository/ContentRepository.java | 2 +- .../email/entity/EmailCertification.java | 94 +++++++++---------- .../EmailCertificationRepository.java | 22 ++--- .../domain/mark/entity/ContentMark.java | 4 +- .../email/controller/EmailController.java | 2 +- .../email/controller/dto/EmailResponse.java | 4 - .../{controller => }/dto/EmailRequest.java | 2 +- .../oopy/coding/email/dto/EmailResponse.java | 4 + .../coding/email/service/EmailService.java | 17 ++-- 17 files changed, 84 insertions(+), 86 deletions(-) delete mode 100644 src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java rename src/main/java/io/oopy/coding/email/{controller => }/dto/EmailRequest.java (92%) create mode 100644 src/main/java/io/oopy/coding/email/dto/EmailResponse.java diff --git a/src/main/java/io/oopy/coding/content/service/getContentDetailService.java b/src/main/java/io/oopy/coding/content/service/getContentDetailService.java index cc46d76..0c70793 100644 --- a/src/main/java/io/oopy/coding/content/service/getContentDetailService.java +++ b/src/main/java/io/oopy/coding/content/service/getContentDetailService.java @@ -5,7 +5,7 @@ import io.oopy.coding.domain.content.entity.Category; import io.oopy.coding.domain.content.entity.Content; import io.oopy.coding.domain.content.entity.ContentCategory; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/io/oopy/coding/domain/comment/dto/CommentDTO.java b/src/main/java/io/oopy/coding/domain/comment/dto/CommentDTO.java index 9bc378a..06c04d1 100644 --- a/src/main/java/io/oopy/coding/domain/comment/dto/CommentDTO.java +++ b/src/main/java/io/oopy/coding/domain/comment/dto/CommentDTO.java @@ -1,7 +1,7 @@ package io.oopy.coding.domain.comment.dto; import io.oopy.coding.domain.content.entity.Content; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.user.entity.User; import lombok.*; import java.time.LocalDateTime; diff --git a/src/main/java/io/oopy/coding/domain/comment/entity/Comment.java b/src/main/java/io/oopy/coding/domain/comment/entity/Comment.java index a8a0b4d..5b3bd81 100644 --- a/src/main/java/io/oopy/coding/domain/comment/entity/Comment.java +++ b/src/main/java/io/oopy/coding/domain/comment/entity/Comment.java @@ -1,7 +1,8 @@ package io.oopy.coding.domain.comment.entity; import io.oopy.coding.domain.content.entity.Content; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.user.entity.User; + import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/io/oopy/coding/domain/content/dto/ContentDTO.java b/src/main/java/io/oopy/coding/domain/content/dto/ContentDTO.java index 70c1f52..daa5a56 100644 --- a/src/main/java/io/oopy/coding/domain/content/dto/ContentDTO.java +++ b/src/main/java/io/oopy/coding/domain/content/dto/ContentDTO.java @@ -1,6 +1,6 @@ package io.oopy.coding.domain.content.dto; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.user.entity.User; import lombok.*; import java.time.LocalDateTime; diff --git a/src/main/java/io/oopy/coding/domain/content/dto/ContentDetailDTO.java b/src/main/java/io/oopy/coding/domain/content/dto/ContentDetailDTO.java index a4761ea..8099de3 100644 --- a/src/main/java/io/oopy/coding/domain/content/dto/ContentDetailDTO.java +++ b/src/main/java/io/oopy/coding/domain/content/dto/ContentDetailDTO.java @@ -1,7 +1,7 @@ package io.oopy.coding.domain.content.dto; import io.oopy.coding.domain.content.entity.ContentCategory; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.user.entity.User; import lombok.*; @Getter diff --git a/src/main/java/io/oopy/coding/domain/content/entity/Category.java b/src/main/java/io/oopy/coding/domain/content/entity/Category.java index 9b49b52..4dd85cf 100644 --- a/src/main/java/io/oopy/coding/domain/content/entity/Category.java +++ b/src/main/java/io/oopy/coding/domain/content/entity/Category.java @@ -1,6 +1,6 @@ package io.oopy.coding.domain.content.entity; -import io.oopy.coding.domain.entity.Auditable; +import io.oopy.coding.domain.model.Auditable; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/io/oopy/coding/domain/content/entity/Content.java b/src/main/java/io/oopy/coding/domain/content/entity/Content.java index 31fffab..a9a4886 100644 --- a/src/main/java/io/oopy/coding/domain/content/entity/Content.java +++ b/src/main/java/io/oopy/coding/domain/content/entity/Content.java @@ -1,8 +1,8 @@ package io.oopy.coding.domain.content.entity; import io.oopy.coding.domain.comment.entity.Comment; -import io.oopy.coding.domain.entity.Auditable; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.model.Auditable; +import io.oopy.coding.domain.user.entity.User; import io.oopy.coding.domain.mark.entity.ContentMark; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/io/oopy/coding/domain/content/entity/ContentCategory.java b/src/main/java/io/oopy/coding/domain/content/entity/ContentCategory.java index c4e1f66..d8eda85 100644 --- a/src/main/java/io/oopy/coding/domain/content/entity/ContentCategory.java +++ b/src/main/java/io/oopy/coding/domain/content/entity/ContentCategory.java @@ -1,6 +1,6 @@ package io.oopy.coding.domain.content.entity; -import io.oopy.coding.domain.entity.Auditable; +import io.oopy.coding.domain.model.Auditable; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/io/oopy/coding/domain/content/repository/ContentRepository.java b/src/main/java/io/oopy/coding/domain/content/repository/ContentRepository.java index 6199c9c..5a4fba0 100644 --- a/src/main/java/io/oopy/coding/domain/content/repository/ContentRepository.java +++ b/src/main/java/io/oopy/coding/domain/content/repository/ContentRepository.java @@ -3,7 +3,7 @@ import io.oopy.coding.domain.content.entity.Category; import io.oopy.coding.domain.content.entity.Content; import io.oopy.coding.domain.content.entity.ContentCategory; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.user.entity.User; import io.oopy.coding.domain.mark.entity.ContentMark; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java b/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java index 09052b4..546a822 100644 --- a/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java +++ b/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java @@ -1,47 +1,47 @@ -package io.oopy.coding.domain.email.entity; - -import io.oopy.coding.common.constant.EmailConstant; -import io.oopy.coding.domain.entity.Auditable; -import jakarta.persistence.*; -import lombok.*; - -import static io.oopy.coding.common.util.Time.getNowUnixTime; - -@Entity -@Getter -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@Builder(access = AccessLevel.PRIVATE) -@Table(name = "EMAIL_CERTIFICATION") -public class EmailCertification extends Auditable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column - private String email; - - @Column - private String githubId; - - @Column - private Integer expiredAt; // unix time - - @Column - private Boolean verify; - - public boolean isExpired() { - return getNowUnixTime() > expiredAt; - } - - public static EmailCertification of(String email, String githubId) { - Integer expireTime = getNowUnixTime() + EmailConstant.EMAIL_EXPIRED_PERIOD_SECONDS; - return EmailCertification.builder() - .email(email) - .githubId(githubId) - .expiredAt(expireTime) - .verify(Boolean.FALSE) - .build(); - } -} +//package io.oopy.coding.domain.email.entity; +// +//import io.oopy.coding.common.constant.EmailConstant; +//import io.oopy.coding.domain.entity.Auditable; +//import jakarta.persistence.*; +//import lombok.*; +// +//import static io.oopy.coding.common.util.Time.getNowUnixTime; +// +//@Entity +//@Getter +//@NoArgsConstructor +//@AllArgsConstructor(access = AccessLevel.PRIVATE) +//@Builder(access = AccessLevel.PRIVATE) +//@Table(name = "EMAIL_CERTIFICATION") +//public class EmailCertification extends Auditable { +// +// @Id +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// private Long id; +// +// @Column +// private String email; +// +// @Column +// private String githubId; +// +// @Column +// private Integer expiredAt; // unix time +// +// @Column +// private Boolean verify; +// +// public boolean isExpired() { +// return getNowUnixTime() > expiredAt; +// } +// +// public static EmailCertification of(String email, String githubId) { +// Integer expireTime = getNowUnixTime() + EmailConstant.EMAIL_EXPIRED_PERIOD_SECONDS; +// return EmailCertification.builder() +// .email(email) +// .githubId(githubId) +// .expiredAt(expireTime) +// .verify(Boolean.FALSE) +// .build(); +// } +//} diff --git a/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java b/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java index ebfaa93..be14298 100644 --- a/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java +++ b/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java @@ -1,11 +1,11 @@ -package io.oopy.coding.domain.email.repository; - -import io.oopy.coding.domain.email.entity.EmailCertification; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface EmailCertificationRepository extends JpaRepository { - - Optional findFirstByGithubIdOrderByExpiredAtDesc(String githubId); -} +//package io.oopy.coding.domain.email.repository; +// +//import io.oopy.coding.domain.email.entity.EmailCertification; +//import org.springframework.data.jpa.repository.JpaRepository; +// +//import java.util.Optional; +// +//public interface EmailCertificationRepository extends JpaRepository { +// +// Optional findFirstByGithubIdOrderByExpiredAtDesc(String githubId); +//} diff --git a/src/main/java/io/oopy/coding/domain/mark/entity/ContentMark.java b/src/main/java/io/oopy/coding/domain/mark/entity/ContentMark.java index ef1bd9d..c907264 100644 --- a/src/main/java/io/oopy/coding/domain/mark/entity/ContentMark.java +++ b/src/main/java/io/oopy/coding/domain/mark/entity/ContentMark.java @@ -1,8 +1,8 @@ package io.oopy.coding.domain.mark.entity; import io.oopy.coding.domain.content.entity.Content; -import io.oopy.coding.domain.entity.Auditable; -import io.oopy.coding.domain.entity.User; +import io.oopy.coding.domain.model.Auditable; +import io.oopy.coding.domain.user.entity.User; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/io/oopy/coding/email/controller/EmailController.java b/src/main/java/io/oopy/coding/email/controller/EmailController.java index 7bc00d1..0b7c158 100644 --- a/src/main/java/io/oopy/coding/email/controller/EmailController.java +++ b/src/main/java/io/oopy/coding/email/controller/EmailController.java @@ -1,6 +1,6 @@ package io.oopy.coding.email.controller; -import io.oopy.coding.email.controller.dto.EmailRequest; +import io.oopy.coding.email.dto.EmailRequest; import io.oopy.coding.email.service.EmailService; import jakarta.mail.MessagingException; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java b/src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java deleted file mode 100644 index f8b63e8..0000000 --- a/src/main/java/io/oopy/coding/email/controller/dto/EmailResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.oopy.coding.email.controller.dto; - -public class EmailResponse { -} diff --git a/src/main/java/io/oopy/coding/email/controller/dto/EmailRequest.java b/src/main/java/io/oopy/coding/email/dto/EmailRequest.java similarity index 92% rename from src/main/java/io/oopy/coding/email/controller/dto/EmailRequest.java rename to src/main/java/io/oopy/coding/email/dto/EmailRequest.java index c21423b..494d319 100644 --- a/src/main/java/io/oopy/coding/email/controller/dto/EmailRequest.java +++ b/src/main/java/io/oopy/coding/email/dto/EmailRequest.java @@ -1,4 +1,4 @@ -package io.oopy.coding.email.controller.dto; +package io.oopy.coding.email.dto; import io.oopy.coding.common.constant.EmailConstant; import io.oopy.coding.email.service.dto.EmailSend; diff --git a/src/main/java/io/oopy/coding/email/dto/EmailResponse.java b/src/main/java/io/oopy/coding/email/dto/EmailResponse.java new file mode 100644 index 0000000..0d0d7b4 --- /dev/null +++ b/src/main/java/io/oopy/coding/email/dto/EmailResponse.java @@ -0,0 +1,4 @@ +package io.oopy.coding.email.dto; + +public class EmailResponse { +} diff --git a/src/main/java/io/oopy/coding/email/service/EmailService.java b/src/main/java/io/oopy/coding/email/service/EmailService.java index f9bc35b..0f206de 100644 --- a/src/main/java/io/oopy/coding/email/service/EmailService.java +++ b/src/main/java/io/oopy/coding/email/service/EmailService.java @@ -1,7 +1,6 @@ package io.oopy.coding.email.service; -import io.oopy.coding.domain.email.entity.EmailCertification; -import io.oopy.coding.domain.email.repository.EmailCertificationRepository; +import io.oopy.coding.common.redis.email.EmailCertificationService; import io.oopy.coding.email.service.dto.EmailSend; import jakarta.mail.Message; import jakarta.mail.MessagingException; @@ -10,23 +9,21 @@ import lombok.RequiredArgsConstructor; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; -import java.util.Optional; +import java.util.UUID; @Service @RequiredArgsConstructor public class EmailService{ private final JavaMailSender javaMailSender; - private final EmailCertificationRepository emailCertificationRepository; + private final EmailCertificationService emailCertificationService; @Transactional public void sendCertMail(EmailSend emailSend) throws MessagingException { checkSendValid(emailSend); - EmailCertification emailCertification = - EmailCertification.of(emailSend.getTargetEmail(), emailSend.getGithubId()); - // persist first - emailCertificationRepository.save(emailCertification); + emailCertificationService.register(UUID.randomUUID().toString(), emailSend.getGithubId()); sendMail(emailSend); } @@ -39,8 +36,8 @@ private void sendMail(EmailSend emailSend) throws MessagingException { } private void checkSendValid(EmailSend emailSend) { - Optional email = emailCertificationRepository.findFirstByGithubIdOrderByExpiredAtDesc(emailSend.getGithubId()); - if (email.isPresent() && !email.get().isExpired()) + String githubId = emailCertificationService.getGithubId(emailSend.getGithubId()); + if (!ObjectUtils.isEmpty(githubId)) throw new RuntimeException("인증 진행중"); } } From ac538e0ad2f913084b804f598036d0a9a49e29c6 Mon Sep 17 00:00:00 2001 From: chanho Date: Sun, 3 Sep 2023 17:35:38 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[UPDATE]=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20-=20=EC=82=AD=EC=A0=9C=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C,=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coding/common/constant/EmailConstant.java | 11 ++-- .../redis/emailcert/EmailCertification.java | 45 ++++++++++++++++ .../EmailCertificationRepository.java | 6 +++ .../emailcert/EmailCertificationService.java | 53 +++++++++++++++++++ .../common/redis/emailcert/dto/EmailCert.java | 40 ++++++++++++++ .../redis/emailcert/dto/EmailCertKey.java | 5 ++ .../email/controller/EmailController.java | 34 +++++++++--- .../oopy/coding/email/dto/EmailRequest.java | 19 +++++-- .../coding/email/service/EmailService.java | 43 ++++++++++++--- .../email/service/dto/EmailCertificate.java | 28 ++++++++++ .../coding/email/service/dto/EmailSend.java | 13 +++-- src/main/resources/application.yml | 7 ++- 12 files changed, 273 insertions(+), 31 deletions(-) create mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java create mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java create mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java create mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java create mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java create mode 100644 src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java diff --git a/src/main/java/io/oopy/coding/common/constant/EmailConstant.java b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java index d11c2c4..c8cb81f 100644 --- a/src/main/java/io/oopy/coding/common/constant/EmailConstant.java +++ b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java @@ -6,8 +6,8 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class EmailConstant { public static final Integer EMAIL_EXPIRED_PERIOD_SECONDS = 600; // 임시 10분 - public static final String TITLE = String.format("[%s] 조직 이메일 인증", GlobalConstant.PROJECT_NAME); - public static final String CONTENT = "\n" + + public static final String CERT_TITLE = String.format("[%s] 조직 이메일 인증", GlobalConstant.PROJECT_NAME); + public static final String CERT_CONTENT = "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + - "\n" + - "링크 버튼\n" + + "
인증하기
\n" + "\n" + "\n" + ""; diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java new file mode 100644 index 0000000..4ea9fe2 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java @@ -0,0 +1,45 @@ +package io.oopy.coding.common.redis.emailcert; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; + +@Getter +@RedisHash("emailAuthenticationToken") +public class EmailCertification { + @Id + private String githubId; + + private String emailToken; + @Setter + private Boolean checked; + + @TimeToLive + private final long ttl; + + @Builder + private EmailCertification(String emailToken, String githubId, long ttl) { + this.emailToken = emailToken; + this.checked = Boolean.FALSE; + this.githubId = githubId; + this.ttl = ttl; + } + + public static EmailCertification of(String emailToken, String githubId, long ttl) { + return new EmailCertification(emailToken, githubId, ttl); + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EmailCertification that)) return false; + return emailToken.equals(that.emailToken); + } + + @Override + public int hashCode() { + return emailToken.hashCode(); + } +} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java new file mode 100644 index 0000000..8bc1781 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java @@ -0,0 +1,6 @@ +package io.oopy.coding.common.redis.emailcert; + +import org.springframework.data.repository.CrudRepository; + +public interface EmailCertificationRepository extends CrudRepository { +} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java new file mode 100644 index 0000000..ee11688 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java @@ -0,0 +1,53 @@ +package io.oopy.coding.common.redis.emailcert; + +import io.oopy.coding.common.redis.emailcert.dto.EmailCert; +import io.oopy.coding.common.redis.emailcert.dto.EmailCertKey; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; + +import java.util.Optional; + +import static io.oopy.coding.common.constant.EmailConstant.EMAIL_EXPIRED_PERIOD_SECONDS; + +@Service +@RequiredArgsConstructor +@Slf4j +public class EmailCertificationService { + + private final EmailCertificationRepository emailCertificationRepository; + private static final long EXPIRED_TIME = EMAIL_EXPIRED_PERIOD_SECONDS; + + @Transactional(readOnly = true) + public EmailCertification getEmailCertification(EmailCertKey emailCertKey) { + Optional emailCert = emailCertificationRepository.findById(emailCertKey.getKey()); + return emailCert.isEmpty() ? null : emailCert.get(); + } + + @Transactional + public void cert(EmailCert.CreateUpdate createUpdate) { + EmailCertification emailCert = getEmailCertification(createUpdate); + if (ObjectUtils.isEmpty(emailCert)) { + throw new RuntimeException("발급된 토큰이 없습니다."); + } + createUpdate.checkSameToken(emailCert.getEmailToken()); + emailCert.setChecked(Boolean.TRUE); + emailCertificationRepository.save(emailCert); + } + + @Transactional + public void register(EmailCert.CreateUpdate createUpdate) { + EmailCertification of = EmailCertification.of(createUpdate.token(), + createUpdate.githubId(), + EXPIRED_TIME); + emailCertificationRepository.save(of); + } + + @Transactional + public void delete(EmailCert.ReadDelete readDelete) { + EmailCertification emailCertification = getEmailCertification(readDelete); + emailCertificationRepository.delete(emailCertification); + } +} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java b/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java new file mode 100644 index 0000000..5f67541 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java @@ -0,0 +1,40 @@ +package io.oopy.coding.common.redis.emailcert.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EmailCert { + + public record ReadDelete(String githubId) implements EmailCertKey { + public static EmailCert.ReadDelete of( + @NotBlank(message = "githubId must not be empty") String githubId) { + return new ReadDelete(githubId); + } + + @Override + public String getKey() { + return githubId; + } + } + + public record CreateUpdate(String githubId, String token) implements EmailCertKey { + @Override + public String getKey() { + return githubId; + } + + public void checkSameToken(String token) { + if(!this.token.equals(token)) { + throw new RuntimeException("token이 일치하지 않습니다."); + } + } + + public static EmailCert.CreateUpdate of( + @NotBlank(message = "githubId must not be empty")String githubId, + @NotBlank(message = "token must not be empty") String token) { + return new CreateUpdate(githubId, token); + } + } +} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java b/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java new file mode 100644 index 0000000..c1cc541 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java @@ -0,0 +1,5 @@ +package io.oopy.coding.common.redis.emailcert.dto; + +public interface EmailCertKey { + public String getKey(); +} diff --git a/src/main/java/io/oopy/coding/email/controller/EmailController.java b/src/main/java/io/oopy/coding/email/controller/EmailController.java index 0b7c158..d646a59 100644 --- a/src/main/java/io/oopy/coding/email/controller/EmailController.java +++ b/src/main/java/io/oopy/coding/email/controller/EmailController.java @@ -3,28 +3,48 @@ import io.oopy.coding.email.dto.EmailRequest; import io.oopy.coding.email.service.EmailService; import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; -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; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; -@RestController +@Controller @RequestMapping("/email") @RequiredArgsConstructor @Slf4j public class EmailController { + @Value("${mail.cert.success-redirect-url}") + private String successRedirectUrl; + private final EmailService emailService; @PostMapping("/send-cert") - public ResponseEntity emailCertificate(@RequestBody EmailRequest.Send emailSend + @ResponseBody + public ResponseEntity sendCertification(@RequestBody EmailRequest.Send emailSend //todo - ,@AuthenticationPrincipal UserDetails userDetails) )throws MessagingException { String githubId = "12345678"; - emailService.sendCertMail(emailSend.toEmailSend(githubId)); + emailService.sendCertMail(emailSend.toCertificateEmailSend(githubId)); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/cert") + @ResponseBody + public ResponseEntity deleteCert( + //todo - ,@AuthenticationPrincipal UserDetails userDetails) + ){ + String githubId = "12345678"; + emailService.deleteCert(githubId); return ResponseEntity.noContent().build(); } + + @GetMapping("/cert/{githubId}/{token}") + public void cert(@PathVariable String githubId, @PathVariable String token, HttpServletResponse httpServletResponse) throws Exception{ + emailService.cert(githubId, token); + httpServletResponse.sendRedirect(successRedirectUrl); + } } diff --git a/src/main/java/io/oopy/coding/email/dto/EmailRequest.java b/src/main/java/io/oopy/coding/email/dto/EmailRequest.java index 494d319..afecc04 100644 --- a/src/main/java/io/oopy/coding/email/dto/EmailRequest.java +++ b/src/main/java/io/oopy/coding/email/dto/EmailRequest.java @@ -1,22 +1,31 @@ package io.oopy.coding.email.dto; import io.oopy.coding.common.constant.EmailConstant; -import io.oopy.coding.email.service.dto.EmailSend; +import io.oopy.coding.email.service.dto.EmailCertificate; import lombok.*; +import static io.oopy.coding.common.constant.EmailConstant.CERT_TITLE; + @NoArgsConstructor(access = AccessLevel.PRIVATE) public class EmailRequest { @Getter public static class Send { + private String targetEmail; - public EmailSend toEmailSend(String githubId) { - return EmailSend.of(this.targetEmail, + public EmailCertificate.Send toCertificateEmailSend(String githubId) { + return EmailCertificate.Send.of(this.targetEmail, githubId, - EmailConstant.TITLE, - EmailConstant.CONTENT + CERT_TITLE, + null ); } } + + @Getter + public static class Cert { + private String githubId; + private String token; + } } diff --git a/src/main/java/io/oopy/coding/email/service/EmailService.java b/src/main/java/io/oopy/coding/email/service/EmailService.java index 0f206de..2a1f6e4 100644 --- a/src/main/java/io/oopy/coding/email/service/EmailService.java +++ b/src/main/java/io/oopy/coding/email/service/EmailService.java @@ -1,43 +1,72 @@ package io.oopy.coding.email.service; -import io.oopy.coding.common.redis.email.EmailCertificationService; +import io.oopy.coding.common.redis.emailcert.EmailCertification; +import io.oopy.coding.common.redis.emailcert.EmailCertificationService; +import io.oopy.coding.common.redis.emailcert.dto.EmailCert; +import io.oopy.coding.email.service.dto.EmailCertificate; import io.oopy.coding.email.service.dto.EmailSend; import jakarta.mail.Message; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import java.util.UUID; +import static io.oopy.coding.common.constant.EmailConstant.CERT_CONTENT; + @Service @RequiredArgsConstructor public class EmailService{ + @Value("${mail.cert.domain}") + private String emailCertDomain; + + private static final String MAIL_CHARACTER_SET = "utf-8"; + private static final String MAIL_SUB_TYPE = "html"; + private final JavaMailSender javaMailSender; private final EmailCertificationService emailCertificationService; @Transactional - public void sendCertMail(EmailSend emailSend) throws MessagingException { + public void sendCertMail(EmailCertificate.Send emailSend) throws MessagingException { checkSendValid(emailSend); - emailCertificationService.register(UUID.randomUUID().toString(), emailSend.getGithubId()); + String token = UUID.randomUUID().toString(); + emailSend.setContent(String.format(CERT_CONTENT, + emailCertDomain, + emailSend.getGithubId(), + token + )); + emailCertificationService.register(new EmailCert.CreateUpdate(emailSend.getGithubId(), token)); sendMail(emailSend); } + @Transactional + public void deleteCert(String githubId) { + emailCertificationService.delete(new EmailCert.ReadDelete(githubId)); + } + + @Transactional + public void cert(String githubId, String token) { + emailCertificationService.cert(new EmailCert.CreateUpdate(githubId, token)); + } + private void sendMail(EmailSend emailSend) throws MessagingException { MimeMessage message = javaMailSender.createMimeMessage(); message.addRecipients(Message.RecipientType.TO, emailSend.getTargetEmail()); message.setSubject(emailSend.getTitle()); - message.setText(emailSend.getContent(), "utf-8", "html"); + message.setText(emailSend.getContent(), MAIL_CHARACTER_SET, MAIL_SUB_TYPE); javaMailSender.send(message); } - private void checkSendValid(EmailSend emailSend) { - String githubId = emailCertificationService.getGithubId(emailSend.getGithubId()); - if (!ObjectUtils.isEmpty(githubId)) + private void checkSendValid(EmailCertificate.Send emailSend) { + EmailCertification emailCertification = emailCertificationService.getEmailCertification(EmailCert.ReadDelete.of(emailSend.getGithubId())); + if (!ObjectUtils.isEmpty(emailCertification)) { throw new RuntimeException("인증 진행중"); + } } } diff --git a/src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java b/src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java new file mode 100644 index 0000000..c47d12f --- /dev/null +++ b/src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java @@ -0,0 +1,28 @@ +package io.oopy.coding.email.service.dto; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EmailCertificate { + + @Getter + public static class Send extends EmailSend{ + + private String githubId; + + public Send(String targetEmail, String githubId, String title, String content) { + super(targetEmail, title, content); + this.githubId = githubId; + } + + public static Send of(String targetEmail, String githubId, String title, String content) { + return new Send(targetEmail, + githubId, + title, + content + ); + } + } +} diff --git a/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java b/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java index e072b6b..a282442 100644 --- a/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java +++ b/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java @@ -1,21 +1,20 @@ package io.oopy.coding.email.service.dto; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; +import lombok.*; @Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class EmailSend { private String targetEmail; - private String githubId; + @Setter private String title; + @Setter private String content; - public static EmailSend of(String targetEmail, String githubId, String title, String content) { + public static EmailSend of(String targetEmail, String title, String content) { return new EmailSend(targetEmail, - githubId, title, content ); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 66611d9..cb46d78 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -64,4 +64,9 @@ jwt: token: # milliseconds 단위 access-expiration-time: 1800000 # 30m (30 * 60 * 1000) - refresh-expiration-time: 604800000 # 7d (7 * 24 * 60 * 60 * 1000) \ No newline at end of file + refresh-expiration-time: 604800000 # 7d (7 * 24 * 60 * 60 * 1000) + +mail: + cert: + domain: ${MAIL_CERT_DOMAIN} + success-redirect-url: ${MAIL_CERT_SUCCESS_URL:http://www.naver.com} \ No newline at end of file From 2cdd23fbc5d37a875857b0d26c02769a613dca04 Mon Sep 17 00:00:00 2001 From: chanho Date: Sun, 3 Sep 2023 17:49:08 +0900 Subject: [PATCH 05/10] [DELETE] email entity --- .../email/entity/EmailCertification.java | 47 ------------------- .../EmailCertificationRepository.java | 11 ----- 2 files changed, 58 deletions(-) delete mode 100644 src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java delete mode 100644 src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java diff --git a/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java b/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java deleted file mode 100644 index 546a822..0000000 --- a/src/main/java/io/oopy/coding/domain/email/entity/EmailCertification.java +++ /dev/null @@ -1,47 +0,0 @@ -//package io.oopy.coding.domain.email.entity; -// -//import io.oopy.coding.common.constant.EmailConstant; -//import io.oopy.coding.domain.entity.Auditable; -//import jakarta.persistence.*; -//import lombok.*; -// -//import static io.oopy.coding.common.util.Time.getNowUnixTime; -// -//@Entity -//@Getter -//@NoArgsConstructor -//@AllArgsConstructor(access = AccessLevel.PRIVATE) -//@Builder(access = AccessLevel.PRIVATE) -//@Table(name = "EMAIL_CERTIFICATION") -//public class EmailCertification extends Auditable { -// -// @Id -// @GeneratedValue(strategy = GenerationType.IDENTITY) -// private Long id; -// -// @Column -// private String email; -// -// @Column -// private String githubId; -// -// @Column -// private Integer expiredAt; // unix time -// -// @Column -// private Boolean verify; -// -// public boolean isExpired() { -// return getNowUnixTime() > expiredAt; -// } -// -// public static EmailCertification of(String email, String githubId) { -// Integer expireTime = getNowUnixTime() + EmailConstant.EMAIL_EXPIRED_PERIOD_SECONDS; -// return EmailCertification.builder() -// .email(email) -// .githubId(githubId) -// .expiredAt(expireTime) -// .verify(Boolean.FALSE) -// .build(); -// } -//} diff --git a/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java b/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java deleted file mode 100644 index be14298..0000000 --- a/src/main/java/io/oopy/coding/domain/email/repository/EmailCertificationRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -//package io.oopy.coding.domain.email.repository; -// -//import io.oopy.coding.domain.email.entity.EmailCertification; -//import org.springframework.data.jpa.repository.JpaRepository; -// -//import java.util.Optional; -// -//public interface EmailCertificationRepository extends JpaRepository { -// -// Optional findFirstByGithubIdOrderByExpiredAtDesc(String githubId); -//} From 9b9d3dd322ff41eb440e7bc48f4542e514cdeb2a Mon Sep 17 00:00:00 2001 From: chanho Date: Mon, 18 Sep 2023 07:55:43 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[UPDATE]=20=EC=A1=B0=EC=A7=81=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=96=B4=EB=8A=90=EC=A0=95=EB=8F=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../coding/common/config/RedisConfig.java | 13 ++ .../coding/common/config/SecurityConfig.java | 2 +- .../coding/common/constant/EmailConstant.java | 2 +- .../email}/dto/EmailSend.java | 2 +- .../common/email/service/EmailService.java | 29 +++++ .../common/jwt/JwtAuthorizationFilter.java | 2 +- .../redis/emailcert/EmailCertification.java | 45 ------- .../EmailCertificationRepository.java | 6 - .../emailcert/EmailCertificationService.java | 53 -------- .../common/redis/emailcert/dto/EmailCert.java | 40 ------ .../redis/emailcert/dto/EmailCertKey.java | 5 - .../OrganizationCertification.java | 48 +++++++ .../OrganizationCertificationRepository.java | 6 + .../OrganizationCertificationService.java | 96 ++++++++++++++ .../dto/OrganizationCertKey.java | 5 + .../dto/OrganizationCertificationDto.java | 121 ++++++++++++++++++ .../organizationcert/dto/OrganizationKey.java | 5 + .../LegacyEnumValueConvertUtils.java | 4 +- .../organization/entity/Organization.java | 27 ++++ .../organization/entity/UserOrganization.java | 41 ++++++ .../repository/OrganizationRepository.java | 10 ++ .../UserOrganizationRepository.java | 11 ++ .../oopy/coding/email/dto/EmailRequest.java | 31 ----- .../oopy/coding/email/dto/EmailResponse.java | 4 - .../coding/email/service/EmailService.java | 72 ----------- .../email/service/dto/EmailCertificate.java | 28 ---- .../controller/EmailCertController.java} | 31 ++--- .../emailcert/dto/EmailCertRequest.java | 37 ++++++ .../emailcert/dto/EmailCertResponse.java | 4 + .../emailcert/service/EmailCertService.java | 80 ++++++++++++ .../service/dto/EmailCertificateDto.java | 32 +++++ .../controller/OrganizationController.java | 48 +++++++ .../organization/dto/OrganizationRequest.java | 9 ++ .../dto/OrganizationResponse.java | 22 ++++ .../service/OrganizationService.java | 71 ++++++++++ .../service/dto/OrganizationDto.java | 64 +++++++++ 37 files changed, 803 insertions(+), 304 deletions(-) rename src/main/java/io/oopy/coding/{email/service => common/email}/dto/EmailSend.java (91%) create mode 100644 src/main/java/io/oopy/coding/common/email/service/EmailService.java delete mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java delete mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java delete mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java delete mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java delete mode 100644 src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java create mode 100644 src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertification.java create mode 100644 src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationRepository.java create mode 100644 src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java create mode 100644 src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertKey.java create mode 100644 src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java create mode 100644 src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationKey.java create mode 100644 src/main/java/io/oopy/coding/domain/organization/entity/Organization.java create mode 100644 src/main/java/io/oopy/coding/domain/organization/entity/UserOrganization.java create mode 100644 src/main/java/io/oopy/coding/domain/organization/repository/OrganizationRepository.java create mode 100644 src/main/java/io/oopy/coding/domain/organization/repository/UserOrganizationRepository.java delete mode 100644 src/main/java/io/oopy/coding/email/dto/EmailRequest.java delete mode 100644 src/main/java/io/oopy/coding/email/dto/EmailResponse.java delete mode 100644 src/main/java/io/oopy/coding/email/service/EmailService.java delete mode 100644 src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java rename src/main/java/io/oopy/coding/{email/controller/EmailController.java => emailcert/controller/EmailCertController.java} (56%) create mode 100644 src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java create mode 100644 src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java create mode 100644 src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java create mode 100644 src/main/java/io/oopy/coding/emailcert/service/dto/EmailCertificateDto.java create mode 100644 src/main/java/io/oopy/coding/organization/controller/OrganizationController.java create mode 100644 src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java create mode 100644 src/main/java/io/oopy/coding/organization/dto/OrganizationResponse.java create mode 100644 src/main/java/io/oopy/coding/organization/service/OrganizationService.java create mode 100644 src/main/java/io/oopy/coding/organization/service/dto/OrganizationDto.java diff --git a/build.gradle b/build.gradle index 5ee6333..f9105a7 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' // jwt implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' diff --git a/src/main/java/io/oopy/coding/common/config/RedisConfig.java b/src/main/java/io/oopy/coding/common/config/RedisConfig.java index c6fd497..ff3a78d 100644 --- a/src/main/java/io/oopy/coding/common/config/RedisConfig.java +++ b/src/main/java/io/oopy/coding/common/config/RedisConfig.java @@ -1,5 +1,6 @@ package io.oopy.coding.common.config; +import io.oopy.coding.common.redis.organizationcert.OrganizationCertification; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -7,7 +8,10 @@ import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @EnableRedisRepositories @@ -24,4 +28,13 @@ public RedisConnectionFactory redisConnectionFactory() { LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder().build(); return new LettuceConnectionFactory(config, clientConfig); } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); // Key: String + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(OrganizationCertification.class)); // Value: 직렬화에 사용할 Object 사용하기 + return redisTemplate; + } } \ No newline at end of file diff --git a/src/main/java/io/oopy/coding/common/config/SecurityConfig.java b/src/main/java/io/oopy/coding/common/config/SecurityConfig.java index 3d17af3..3152aa7 100644 --- a/src/main/java/io/oopy/coding/common/config/SecurityConfig.java +++ b/src/main/java/io/oopy/coding/common/config/SecurityConfig.java @@ -30,7 +30,7 @@ public class SecurityConfig { "/test/**", "/v3/api-docs/**", "/swagger-ui/**", "/swagger", "/api/v1/users/login", "/api/v1/users/refresh", - "/email/**" + "/email-cert/**", "/organization/**" }; private final UserDetailServiceImpl userDetailServiceImpl; diff --git a/src/main/java/io/oopy/coding/common/constant/EmailConstant.java b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java index c8cb81f..436436f 100644 --- a/src/main/java/io/oopy/coding/common/constant/EmailConstant.java +++ b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java @@ -34,7 +34,7 @@ public class EmailConstant { "\n" + "\n" + "\n" + - "
인증하기
\n" + + "
인증하기
\n" + "\n" + "\n" + ""; diff --git a/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java b/src/main/java/io/oopy/coding/common/email/dto/EmailSend.java similarity index 91% rename from src/main/java/io/oopy/coding/email/service/dto/EmailSend.java rename to src/main/java/io/oopy/coding/common/email/dto/EmailSend.java index a282442..35efeb9 100644 --- a/src/main/java/io/oopy/coding/email/service/dto/EmailSend.java +++ b/src/main/java/io/oopy/coding/common/email/dto/EmailSend.java @@ -1,4 +1,4 @@ -package io.oopy.coding.email.service.dto; +package io.oopy.coding.common.email.dto; import lombok.*; diff --git a/src/main/java/io/oopy/coding/common/email/service/EmailService.java b/src/main/java/io/oopy/coding/common/email/service/EmailService.java new file mode 100644 index 0000000..10c7144 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/email/service/EmailService.java @@ -0,0 +1,29 @@ +package io.oopy.coding.common.email.service; + +import io.oopy.coding.common.email.dto.EmailSend; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +/** + * email db를 만들 가능성이 있으므로, service + */ +@Service +@RequiredArgsConstructor +public class EmailService { + + private final JavaMailSender javaMailSender; + private static final String MAIL_CHARACTER_SET = "utf-8"; + private static final String MAIL_SUB_TYPE = "html"; + + public void sendMail(EmailSend emailSend) throws MessagingException { + MimeMessage message = javaMailSender.createMimeMessage(); + message.addRecipients(Message.RecipientType.TO, emailSend.getTargetEmail()); + message.setSubject(emailSend.getTitle()); + message.setText(emailSend.getContent(), MAIL_CHARACTER_SET, MAIL_SUB_TYPE); + javaMailSender.send(message); + } +} diff --git a/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java b/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java index 423acf9..8ccdc9d 100644 --- a/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java +++ b/src/main/java/io/oopy/coding/common/jwt/JwtAuthorizationFilter.java @@ -51,7 +51,7 @@ public class JwtAuthorizationFilter extends OncePerRequestFilter { "/api/v1/users/refresh", "/v3/api-docs/**", "/swagger-ui/**", "/swagger", "/favicon.ico", - "/email/**" + "/email-cert/**", "/organization/**" ); @Override diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java deleted file mode 100644 index 4ea9fe2..0000000 --- a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertification.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.oopy.coding.common.redis.emailcert; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.springframework.data.annotation.Id; -import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.TimeToLive; - -@Getter -@RedisHash("emailAuthenticationToken") -public class EmailCertification { - @Id - private String githubId; - - private String emailToken; - @Setter - private Boolean checked; - - @TimeToLive - private final long ttl; - - @Builder - private EmailCertification(String emailToken, String githubId, long ttl) { - this.emailToken = emailToken; - this.checked = Boolean.FALSE; - this.githubId = githubId; - this.ttl = ttl; - } - - public static EmailCertification of(String emailToken, String githubId, long ttl) { - return new EmailCertification(emailToken, githubId, ttl); - } - - @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof EmailCertification that)) return false; - return emailToken.equals(that.emailToken); - } - - @Override - public int hashCode() { - return emailToken.hashCode(); - } -} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java deleted file mode 100644 index 8bc1781..0000000 --- a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.oopy.coding.common.redis.emailcert; - -import org.springframework.data.repository.CrudRepository; - -public interface EmailCertificationRepository extends CrudRepository { -} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java b/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java deleted file mode 100644 index ee11688..0000000 --- a/src/main/java/io/oopy/coding/common/redis/emailcert/EmailCertificationService.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.oopy.coding.common.redis.emailcert; - -import io.oopy.coding.common.redis.emailcert.dto.EmailCert; -import io.oopy.coding.common.redis.emailcert.dto.EmailCertKey; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.ObjectUtils; - -import java.util.Optional; - -import static io.oopy.coding.common.constant.EmailConstant.EMAIL_EXPIRED_PERIOD_SECONDS; - -@Service -@RequiredArgsConstructor -@Slf4j -public class EmailCertificationService { - - private final EmailCertificationRepository emailCertificationRepository; - private static final long EXPIRED_TIME = EMAIL_EXPIRED_PERIOD_SECONDS; - - @Transactional(readOnly = true) - public EmailCertification getEmailCertification(EmailCertKey emailCertKey) { - Optional emailCert = emailCertificationRepository.findById(emailCertKey.getKey()); - return emailCert.isEmpty() ? null : emailCert.get(); - } - - @Transactional - public void cert(EmailCert.CreateUpdate createUpdate) { - EmailCertification emailCert = getEmailCertification(createUpdate); - if (ObjectUtils.isEmpty(emailCert)) { - throw new RuntimeException("발급된 토큰이 없습니다."); - } - createUpdate.checkSameToken(emailCert.getEmailToken()); - emailCert.setChecked(Boolean.TRUE); - emailCertificationRepository.save(emailCert); - } - - @Transactional - public void register(EmailCert.CreateUpdate createUpdate) { - EmailCertification of = EmailCertification.of(createUpdate.token(), - createUpdate.githubId(), - EXPIRED_TIME); - emailCertificationRepository.save(of); - } - - @Transactional - public void delete(EmailCert.ReadDelete readDelete) { - EmailCertification emailCertification = getEmailCertification(readDelete); - emailCertificationRepository.delete(emailCertification); - } -} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java b/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java deleted file mode 100644 index 5f67541..0000000 --- a/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCert.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.oopy.coding.common.redis.emailcert.dto; - -import jakarta.validation.constraints.NotBlank; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class EmailCert { - - public record ReadDelete(String githubId) implements EmailCertKey { - public static EmailCert.ReadDelete of( - @NotBlank(message = "githubId must not be empty") String githubId) { - return new ReadDelete(githubId); - } - - @Override - public String getKey() { - return githubId; - } - } - - public record CreateUpdate(String githubId, String token) implements EmailCertKey { - @Override - public String getKey() { - return githubId; - } - - public void checkSameToken(String token) { - if(!this.token.equals(token)) { - throw new RuntimeException("token이 일치하지 않습니다."); - } - } - - public static EmailCert.CreateUpdate of( - @NotBlank(message = "githubId must not be empty")String githubId, - @NotBlank(message = "token must not be empty") String token) { - return new CreateUpdate(githubId, token); - } - } -} diff --git a/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java b/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java deleted file mode 100644 index c1cc541..0000000 --- a/src/main/java/io/oopy/coding/common/redis/emailcert/dto/EmailCertKey.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.oopy.coding.common.redis.emailcert.dto; - -public interface EmailCertKey { - public String getKey(); -} diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertification.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertification.java new file mode 100644 index 0000000..29ba41b --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertification.java @@ -0,0 +1,48 @@ +package io.oopy.coding.common.redis.organizationcert; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.*; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +@Getter +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@EqualsAndHashCode +public class OrganizationCertification { + + private String id; + private String certToken; + private String userEmail; + private String organizationCode; + private String organizationName; + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime expiredAt; + private Boolean expired; + + @Builder + private OrganizationCertification(String certToken, String id, String userEmail, + String organizationCode, String organizationName, long ttl) { + this.certToken = certToken; + this.id = id; + this.userEmail = userEmail; + this.organizationCode = organizationCode; + this.organizationName = organizationName; + this.expiredAt = LocalDateTime.now().plus(ttl, ChronoUnit.SECONDS); + } + + public static OrganizationCertification of(String certToken, String id, String userEmail, + String organizationCode, String organizationName, long ttl) { + return new OrganizationCertification(certToken, id, userEmail, + organizationCode, organizationName, ttl); + } + + public boolean isExpired() { + return this.expiredAt.isAfter(LocalDateTime.now()); + } +} diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationRepository.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationRepository.java new file mode 100644 index 0000000..c5356bb --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationRepository.java @@ -0,0 +1,6 @@ +package io.oopy.coding.common.redis.organizationcert; + +import org.springframework.data.repository.CrudRepository; + +public interface OrganizationCertificationRepository extends CrudRepository { +} diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java new file mode 100644 index 0000000..5e36076 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java @@ -0,0 +1,96 @@ +package io.oopy.coding.common.redis.organizationcert; + +import io.oopy.coding.common.redis.organizationcert.dto.OrganizationCertificationDto; +import io.oopy.coding.common.redis.organizationcert.dto.OrganizationCertKey; +import io.oopy.coding.common.redis.organizationcert.dto.OrganizationKey; +import io.oopy.coding.organization.service.OrganizationService; +import io.oopy.coding.organization.service.dto.OrganizationDto; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; + +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class OrganizationCertificationService { + + private final ListOperations listOperations; + private final RedisTemplate redisTemplate; + private final OrganizationService organizationService; + private static final long EXPIRED_TIME = 600; + + public OrganizationCertificationService(RedisTemplate redisTemplate, + OrganizationService organizationService) { + this.listOperations = redisTemplate.opsForList(); + this.redisTemplate = redisTemplate; + this.organizationService = organizationService; + } + + @Transactional(readOnly = true) + public List getOrganizationCertifications(OrganizationKey organizationCertKey) { + List organizationCertifications = listOperations.range(organizationCertKey.getKey(), 0, -1); + if (ObjectUtils.isEmpty(organizationCertifications)) + return new ArrayList<>(); + List filteredOrganizationCertifications = organizationCertifications.stream() + .filter(v -> v.isExpired()) + .collect(Collectors.toList()); + return ObjectUtils.isEmpty(filteredOrganizationCertifications) ? + new ArrayList<>() : + filteredOrganizationCertifications; + } + + @Transactional + public void register(OrganizationCertificationDto.Register register) { + if(organizationService.isUserAlreadyRegistered( + OrganizationDto.Cert.of(register.getOrganizationCode(), + Long.parseLong(register.userId()), + register.userEmail() + ))) { + throw new RuntimeException("이미 인증된 조직입니다."); + } + Optional checkOrganizationCertification = getOrganizationCertification(register); + if (checkOrganizationCertification.isPresent()) { + throw new RuntimeException("인증 진행중입니다."); + } + OrganizationCertification organizationCertification = OrganizationCertification.of(register.token(), + register.userId(), + register.userEmail(), + register.code(), + register.organizationName(), + EXPIRED_TIME + ); + listOperations.rightPush(register.getKey(), organizationCertification); + ZoneId zoneId = ZoneId.systemDefault(); + redisTemplate.expireAt(register.getKey(), organizationCertification.getExpiredAt().atZone(zoneId) + .toInstant()); + } + + @Transactional + public void delete(OrganizationCertKey organizationCertKey) { + Optional organizationCertification = getOrganizationCertification(organizationCertKey); + if (organizationCertification.isEmpty()) { + throw new RuntimeException("인증 내역 없음."); + } + listOperations.remove(organizationCertKey.getKey(), 0, organizationCertification.get()); + } + + public Optional getOrganizationCertification(OrganizationCertKey cert) { + List organizationCertificationList = getOrganizationCertifications(cert); + if (ObjectUtils.isEmpty(organizationCertificationList)) { + return Optional.empty(); + } + Optional organizationCertification = organizationCertificationList.stream() + .filter(v -> v.getOrganizationCode() != null && + v.getOrganizationCode().equals(cert.getOrganizationCode())) + .findFirst(); + return organizationCertification; + } +} \ No newline at end of file diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertKey.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertKey.java new file mode 100644 index 0000000..3833402 --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertKey.java @@ -0,0 +1,5 @@ +package io.oopy.coding.common.redis.organizationcert.dto; + +public interface OrganizationCertKey extends OrganizationKey{ + String getOrganizationCode(); +} diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java new file mode 100644 index 0000000..be698aa --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java @@ -0,0 +1,121 @@ +package io.oopy.coding.common.redis.organizationcert.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class OrganizationCertificationDto { + + private static final String KEY_PREFIX = "org-cert:"; + + public record Register(String userId, String token, String code, + String organizationName, String userEmail) implements OrganizationCertKey { + + @Override + public String getKey() { + return KEY_PREFIX + userId; + } + + @Override + public String getOrganizationCode() { + return code; + } + + public void checkSameToken(String token) { + if(!this.token.equals(token)) { + throw new RuntimeException("token이 일치하지 않습니다."); + } + } + + public static OrganizationCertificationDto.Register of( + @NotBlank(message = "userId must not be empty")String userId, + @NotBlank(message = "token must not be empty") String token, + @NotBlank(message = "code must not be empty") String code, + @NotBlank(message = "organizationName must not be empty") String organizationName, + @NotBlank(message = "email must not be empty") String email + ) { + return new OrganizationCertificationDto.Register(userId, token, code, organizationName, email); + } + } + + public record Cert(String userId, String token, String code) implements OrganizationCertKey { + + @Override + public String getKey() { + return KEY_PREFIX + userId; + } + + @Override + public String getOrganizationCode() { + return code; + } + + public void checkSameToken(String token) { + if(!this.token.equals(token)) { + throw new RuntimeException("token이 일치하지 않습니다."); + } + } + + public static OrganizationCertificationDto.Cert of( + @NotBlank(message = "userId must not be empty")String userId, + @NotBlank(message = "token must not be empty") String token, + @NotBlank(message = "code must not be empty") String code + ) { + return new OrganizationCertificationDto.Cert(userId, token, code); + } + } + + public record Get(String userId, String code) implements OrganizationCertKey { + + public static OrganizationCertificationDto.Get of( + @NotBlank(message = "userId must not be empty") String userId, + @NotBlank(message = "code must not be empty") String code + ) { + return new OrganizationCertificationDto.Get(userId, code); + } + + @Override + public String getKey() { + return KEY_PREFIX + userId; + } + + @Override + public String getOrganizationCode() { + return code; + } + } + + public record Delete(String userId, String code) implements OrganizationCertKey { + + public static OrganizationCertificationDto.Delete of( + @NotBlank(message = "userId must not be empty") String userId, + @NotBlank(message = "code must not be empty") String code + ) { + return new OrganizationCertificationDto.Delete(userId, code); + } + + @Override + public String getKey() { + return KEY_PREFIX + userId; + } + + @Override + public String getOrganizationCode() { + return code; + } + } + + public record Key(String userId) implements OrganizationKey { + public static OrganizationCertificationDto.Key of( + @NotBlank(message = "userId must not be empty") String userId + ) { + return new OrganizationCertificationDto.Key(userId); + } + + @Override + public String getKey() { + return KEY_PREFIX + userId; + } + } +} diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationKey.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationKey.java new file mode 100644 index 0000000..3091d9a --- /dev/null +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationKey.java @@ -0,0 +1,5 @@ +package io.oopy.coding.common.redis.organizationcert.dto; + +public interface OrganizationKey { + String getKey(); +} diff --git a/src/main/java/io/oopy/coding/common/utils/converter/LegacyEnumValueConvertUtils.java b/src/main/java/io/oopy/coding/common/utils/converter/LegacyEnumValueConvertUtils.java index 3b963c2..dc0474e 100644 --- a/src/main/java/io/oopy/coding/common/utils/converter/LegacyEnumValueConvertUtils.java +++ b/src/main/java/io/oopy/coding/common/utils/converter/LegacyEnumValueConvertUtils.java @@ -14,7 +14,9 @@ public class LegacyEnumValueConvertUtils { public static & LegacyCommonType> T ofLegacyCode(Class enumClass, String code) { if (!StringUtils.hasText(code)) return null; return EnumSet.allOf(enumClass).stream() - .filter(e -> e.getCode().equals(code)) + // fixme 확인 필요 +// .filter(e -> e.getCode().equals(code)) + .filter(e -> e.name().equals(code)) .findFirst() .orElseThrow(() -> new IllegalArgumentException( String.format("enum=[%s], code=[%s]가 존재하지 않습니다.", enumClass.getName(), code))); // TODO : 공통 예외로 변경 diff --git a/src/main/java/io/oopy/coding/domain/organization/entity/Organization.java b/src/main/java/io/oopy/coding/domain/organization/entity/Organization.java new file mode 100644 index 0000000..f37f629 --- /dev/null +++ b/src/main/java/io/oopy/coding/domain/organization/entity/Organization.java @@ -0,0 +1,27 @@ +package io.oopy.coding.domain.organization.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name="ORGANIZATION") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Organization { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column + private String name; + + @Column + private String code; + + @Column + private String description; + +} diff --git a/src/main/java/io/oopy/coding/domain/organization/entity/UserOrganization.java b/src/main/java/io/oopy/coding/domain/organization/entity/UserOrganization.java new file mode 100644 index 0000000..c8522bf --- /dev/null +++ b/src/main/java/io/oopy/coding/domain/organization/entity/UserOrganization.java @@ -0,0 +1,41 @@ +package io.oopy.coding.domain.organization.entity; + +import io.oopy.coding.domain.model.Auditable; +import io.oopy.coding.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name="USER_ORGANIZATION") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserOrganization extends Auditable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "organization_id") + private Organization organization; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @Column(name = "defaults") + private Integer defaults; + + @Column(name = "email") + private String email; + + public UserOrganization(Organization organization, User user, String email) { + this.organization = organization; + this.user = user; + this.email = email; + } + + public static UserOrganization of(Organization organization, User user, String email) { + return new UserOrganization(organization, user, email); + } +} diff --git a/src/main/java/io/oopy/coding/domain/organization/repository/OrganizationRepository.java b/src/main/java/io/oopy/coding/domain/organization/repository/OrganizationRepository.java new file mode 100644 index 0000000..9164a5e --- /dev/null +++ b/src/main/java/io/oopy/coding/domain/organization/repository/OrganizationRepository.java @@ -0,0 +1,10 @@ +package io.oopy.coding.domain.organization.repository; + +import io.oopy.coding.domain.organization.entity.Organization; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface OrganizationRepository extends JpaRepository { + Optional findByCode(String code); +} diff --git a/src/main/java/io/oopy/coding/domain/organization/repository/UserOrganizationRepository.java b/src/main/java/io/oopy/coding/domain/organization/repository/UserOrganizationRepository.java new file mode 100644 index 0000000..5ffae6d --- /dev/null +++ b/src/main/java/io/oopy/coding/domain/organization/repository/UserOrganizationRepository.java @@ -0,0 +1,11 @@ +package io.oopy.coding.domain.organization.repository; + +import io.oopy.coding.domain.organization.entity.UserOrganization; +import io.oopy.coding.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserOrganizationRepository extends JpaRepository { + List findByUser(User user); +} diff --git a/src/main/java/io/oopy/coding/email/dto/EmailRequest.java b/src/main/java/io/oopy/coding/email/dto/EmailRequest.java deleted file mode 100644 index afecc04..0000000 --- a/src/main/java/io/oopy/coding/email/dto/EmailRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.oopy.coding.email.dto; - -import io.oopy.coding.common.constant.EmailConstant; -import io.oopy.coding.email.service.dto.EmailCertificate; -import lombok.*; - -import static io.oopy.coding.common.constant.EmailConstant.CERT_TITLE; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class EmailRequest { - - @Getter - public static class Send { - - private String targetEmail; - - public EmailCertificate.Send toCertificateEmailSend(String githubId) { - return EmailCertificate.Send.of(this.targetEmail, - githubId, - CERT_TITLE, - null - ); - } - } - - @Getter - public static class Cert { - private String githubId; - private String token; - } -} diff --git a/src/main/java/io/oopy/coding/email/dto/EmailResponse.java b/src/main/java/io/oopy/coding/email/dto/EmailResponse.java deleted file mode 100644 index 0d0d7b4..0000000 --- a/src/main/java/io/oopy/coding/email/dto/EmailResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.oopy.coding.email.dto; - -public class EmailResponse { -} diff --git a/src/main/java/io/oopy/coding/email/service/EmailService.java b/src/main/java/io/oopy/coding/email/service/EmailService.java deleted file mode 100644 index 2a1f6e4..0000000 --- a/src/main/java/io/oopy/coding/email/service/EmailService.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.oopy.coding.email.service; - -import io.oopy.coding.common.redis.emailcert.EmailCertification; -import io.oopy.coding.common.redis.emailcert.EmailCertificationService; -import io.oopy.coding.common.redis.emailcert.dto.EmailCert; -import io.oopy.coding.email.service.dto.EmailCertificate; -import io.oopy.coding.email.service.dto.EmailSend; -import jakarta.mail.Message; -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeMessage; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.stereotype.Service; -import org.springframework.util.ObjectUtils; - -import java.util.UUID; - -import static io.oopy.coding.common.constant.EmailConstant.CERT_CONTENT; - -@Service -@RequiredArgsConstructor -public class EmailService{ - - @Value("${mail.cert.domain}") - private String emailCertDomain; - - private static final String MAIL_CHARACTER_SET = "utf-8"; - private static final String MAIL_SUB_TYPE = "html"; - - private final JavaMailSender javaMailSender; - private final EmailCertificationService emailCertificationService; - - @Transactional - public void sendCertMail(EmailCertificate.Send emailSend) throws MessagingException { - checkSendValid(emailSend); - String token = UUID.randomUUID().toString(); - emailSend.setContent(String.format(CERT_CONTENT, - emailCertDomain, - emailSend.getGithubId(), - token - )); - emailCertificationService.register(new EmailCert.CreateUpdate(emailSend.getGithubId(), token)); - sendMail(emailSend); - } - - @Transactional - public void deleteCert(String githubId) { - emailCertificationService.delete(new EmailCert.ReadDelete(githubId)); - } - - @Transactional - public void cert(String githubId, String token) { - emailCertificationService.cert(new EmailCert.CreateUpdate(githubId, token)); - } - - private void sendMail(EmailSend emailSend) throws MessagingException { - MimeMessage message = javaMailSender.createMimeMessage(); - message.addRecipients(Message.RecipientType.TO, emailSend.getTargetEmail()); - message.setSubject(emailSend.getTitle()); - message.setText(emailSend.getContent(), MAIL_CHARACTER_SET, MAIL_SUB_TYPE); - javaMailSender.send(message); - } - - private void checkSendValid(EmailCertificate.Send emailSend) { - EmailCertification emailCertification = emailCertificationService.getEmailCertification(EmailCert.ReadDelete.of(emailSend.getGithubId())); - if (!ObjectUtils.isEmpty(emailCertification)) { - throw new RuntimeException("인증 진행중"); - } - } -} diff --git a/src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java b/src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java deleted file mode 100644 index c47d12f..0000000 --- a/src/main/java/io/oopy/coding/email/service/dto/EmailCertificate.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.oopy.coding.email.service.dto; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class EmailCertificate { - - @Getter - public static class Send extends EmailSend{ - - private String githubId; - - public Send(String targetEmail, String githubId, String title, String content) { - super(targetEmail, title, content); - this.githubId = githubId; - } - - public static Send of(String targetEmail, String githubId, String title, String content) { - return new Send(targetEmail, - githubId, - title, - content - ); - } - } -} diff --git a/src/main/java/io/oopy/coding/email/controller/EmailController.java b/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java similarity index 56% rename from src/main/java/io/oopy/coding/email/controller/EmailController.java rename to src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java index d646a59..a425e87 100644 --- a/src/main/java/io/oopy/coding/email/controller/EmailController.java +++ b/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java @@ -1,7 +1,7 @@ -package io.oopy.coding.email.controller; +package io.oopy.coding.emailcert.controller; -import io.oopy.coding.email.dto.EmailRequest; -import io.oopy.coding.email.service.EmailService; +import io.oopy.coding.emailcert.dto.EmailCertRequest; +import io.oopy.coding.emailcert.service.EmailCertService; import jakarta.mail.MessagingException; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -12,39 +12,40 @@ import org.springframework.web.bind.annotation.*; @Controller -@RequestMapping("/email") +@RequestMapping("/email-cert") @RequiredArgsConstructor @Slf4j -public class EmailController { +public class EmailCertController { @Value("${mail.cert.success-redirect-url}") private String successRedirectUrl; - private final EmailService emailService; + private final EmailCertService emailService; - @PostMapping("/send-cert") + @PostMapping("/send") @ResponseBody - public ResponseEntity sendCertification(@RequestBody EmailRequest.Send emailSend + public ResponseEntity sendCertification(@RequestBody EmailCertRequest.Send emailSend //todo - ,@AuthenticationPrincipal UserDetails userDetails) )throws MessagingException { - String githubId = "12345678"; - emailService.sendCertMail(emailSend.toCertificateEmailSend(githubId)); + String userId = "1"; + emailService.sendCertMail(emailSend.toCertificateEmailSend(userId)); return ResponseEntity.noContent().build(); } @DeleteMapping("/cert") @ResponseBody public ResponseEntity deleteCert( + @RequestBody EmailCertRequest.Remove cert //todo - ,@AuthenticationPrincipal UserDetails userDetails) ){ - String githubId = "12345678"; - emailService.deleteCert(githubId); + String userId = "1"; + emailService.deleteCert(userId, cert.getCode()); return ResponseEntity.noContent().build(); } - @GetMapping("/cert/{githubId}/{token}") - public void cert(@PathVariable String githubId, @PathVariable String token, HttpServletResponse httpServletResponse) throws Exception{ - emailService.cert(githubId, token); + @GetMapping("/cert/{userId}/{token}/{code}") + public void cert(@PathVariable String userId, @PathVariable String token, @PathVariable String code, HttpServletResponse httpServletResponse) throws Exception{ + emailService.cert(userId, token, code); httpServletResponse.sendRedirect(successRedirectUrl); } } diff --git a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java new file mode 100644 index 0000000..ef30fcc --- /dev/null +++ b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java @@ -0,0 +1,37 @@ +package io.oopy.coding.emailcert.dto; + +import io.oopy.coding.emailcert.service.dto.EmailCertificateDto; +import lombok.*; + +import static io.oopy.coding.common.constant.EmailConstant.CERT_TITLE; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EmailCertRequest { + + @Getter + public static class Send { + + private String targetEmail; + private String organizationCode; + + public EmailCertificateDto.Send toCertificateEmailSend(String githubId) { + return EmailCertificateDto.Send.of(this.targetEmail, + githubId, + CERT_TITLE, + null, + this.organizationCode + ); + } + } + + @Getter + public static class Cert { + private String token; + private String code; + } + + @Getter + public static class Remove { + private String code; + } +} diff --git a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java new file mode 100644 index 0000000..9c504e1 --- /dev/null +++ b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java @@ -0,0 +1,4 @@ +package io.oopy.coding.emailcert.dto; + +public class EmailCertResponse { +} diff --git a/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java b/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java new file mode 100644 index 0000000..18681fd --- /dev/null +++ b/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java @@ -0,0 +1,80 @@ +package io.oopy.coding.emailcert.service; + +import io.oopy.coding.common.email.service.EmailService; +import io.oopy.coding.common.redis.organizationcert.OrganizationCertification; +import io.oopy.coding.common.redis.organizationcert.OrganizationCertificationService; +import io.oopy.coding.common.redis.organizationcert.dto.OrganizationCertificationDto; +import io.oopy.coding.emailcert.service.dto.EmailCertificateDto; +import io.oopy.coding.organization.service.OrganizationService; +import io.oopy.coding.organization.service.dto.OrganizationDto; +import jakarta.mail.MessagingException; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.UUID; + +import static io.oopy.coding.common.constant.EmailConstant.CERT_CONTENT; + +@Service +@RequiredArgsConstructor +public class EmailCertService { + + @Value("${mail.cert.domain}") + private String emailCertDomain; + private final OrganizationCertificationService organizationCertificationService; + private final EmailService emailService; + private final OrganizationService organizationService; + + + @Transactional + public void sendCertMail(EmailCertificateDto.Send emailSend) throws MessagingException { + checkSendValid(emailSend); + String token = UUID.randomUUID().toString(); + emailSend.setContent(String.format(CERT_CONTENT, + emailCertDomain, + emailSend.getUserId(), + token, + emailSend.getOrganizationCode() + )); + OrganizationDto.Organization organizationByCode = organizationService.getOrganizationByCode(emailSend.getOrganizationCode()); + organizationCertificationService.register( + new OrganizationCertificationDto.Register( + emailSend.getUserId(), token, emailSend.getOrganizationCode(), + organizationByCode.getName(),emailSend.getTargetEmail() + )); + emailService.sendMail(emailSend); + } + + @Transactional + public void deleteCert(String githubId, String code) { + organizationCertificationService.delete(OrganizationCertificationDto.Delete.of(githubId, code)); + } + + @Transactional + public void cert(String userId, String token, String code) { + Optional emailCertification = organizationCertificationService.getOrganizationCertification( + OrganizationCertificationDto.Cert.of(userId, token, code)); + if (emailCertification.isEmpty()) { + throw new RuntimeException("진행중이지 않습니다."); + } + if (!token.equals(emailCertification.get().getCertToken())) { + throw new RuntimeException("토큰 정보가 올바르지 않습니다."); + } + organizationService.setUserOrganization( + OrganizationDto.Cert.of(code, Long.parseLong(userId), emailCertification.get().getUserEmail())); + organizationCertificationService.delete( + OrganizationCertificationDto.Delete.of(userId, code) + ); + } + + private void checkSendValid(EmailCertificateDto.Send emailSend) { + Optional emailCertificationBySet = organizationCertificationService.getOrganizationCertification( + OrganizationCertificationDto.Get.of(emailSend.getUserId(), emailSend.getOrganizationCode())); + if (emailCertificationBySet.isPresent()) { + throw new RuntimeException("인증 진행중"); + } + } +} diff --git a/src/main/java/io/oopy/coding/emailcert/service/dto/EmailCertificateDto.java b/src/main/java/io/oopy/coding/emailcert/service/dto/EmailCertificateDto.java new file mode 100644 index 0000000..275e2d2 --- /dev/null +++ b/src/main/java/io/oopy/coding/emailcert/service/dto/EmailCertificateDto.java @@ -0,0 +1,32 @@ +package io.oopy.coding.emailcert.service.dto; + +import io.oopy.coding.common.email.dto.EmailSend; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EmailCertificateDto { + + @Getter + public static class Send extends EmailSend { + + private String userId; + private String organizationCode; + + public Send(String targetEmail, String userId, String title, String content, String organizationCode) { + super(targetEmail, title, content); + this.userId = userId; + this.organizationCode = organizationCode; + } + + public static Send of(String targetEmail, String githubId, String title, String content, String organizationCode) { + return new Send(targetEmail, + githubId, + title, + content, + organizationCode + ); + } + } +} diff --git a/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java b/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java new file mode 100644 index 0000000..c67cc2f --- /dev/null +++ b/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java @@ -0,0 +1,48 @@ +package io.oopy.coding.organization.controller; + +import io.oopy.coding.common.redis.organizationcert.OrganizationCertification; +import io.oopy.coding.common.redis.organizationcert.OrganizationCertificationService; +import io.oopy.coding.common.redis.organizationcert.dto.OrganizationCertificationDto; +import io.oopy.coding.organization.dto.OrganizationResponse; +import io.oopy.coding.organization.service.OrganizationService; +import io.oopy.coding.organization.service.dto.OrganizationDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/organization") +@RequiredArgsConstructor +@Slf4j +public class OrganizationController { + + private final OrganizationService organizationService; + private final OrganizationCertificationService organizationCertificationService; + + @GetMapping("/users/{id}") + public ResponseEntity > getList(@PathVariable Long id) { + id = 1L; + List userOrganizations = organizationService.getUserOrganizations(id); + List response = userOrganizations + .stream() + .map(v->OrganizationResponse.UserOrganization.of(v.getCode(), v.getName(), v.getEmail())) + .collect(Collectors.toList()); + return ResponseEntity.ok(response); + } + + @GetMapping("/pending/users/{id}") + public ResponseEntity > getPendingList(@PathVariable Long id) { + id = 1L; + OrganizationCertificationDto.Key key = OrganizationCertificationDto.Key.of(id.toString()); + List organizationCertifications = organizationCertificationService.getOrganizationCertifications(key); + List response = organizationCertifications + .stream() + .map(v -> OrganizationResponse.UserOrganization.of(v.getOrganizationCode(), v.getOrganizationName(), v.getUserEmail())) + .collect(Collectors.toList()); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java b/src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java new file mode 100644 index 0000000..62a38ff --- /dev/null +++ b/src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java @@ -0,0 +1,9 @@ +package io.oopy.coding.organization.dto; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class OrganizationRequest { +} diff --git a/src/main/java/io/oopy/coding/organization/dto/OrganizationResponse.java b/src/main/java/io/oopy/coding/organization/dto/OrganizationResponse.java new file mode 100644 index 0000000..43e276c --- /dev/null +++ b/src/main/java/io/oopy/coding/organization/dto/OrganizationResponse.java @@ -0,0 +1,22 @@ +package io.oopy.coding.organization.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class OrganizationResponse { + + @Getter + @AllArgsConstructor(access = AccessLevel.PRIVATE) + public static class UserOrganization { + private String code; + private String organizationName; + private String userEmail; + + public static UserOrganization of(String code, String organizationName, String userEmail) { + return new UserOrganization(code, organizationName, userEmail); + } + } +} diff --git a/src/main/java/io/oopy/coding/organization/service/OrganizationService.java b/src/main/java/io/oopy/coding/organization/service/OrganizationService.java new file mode 100644 index 0000000..7f410d6 --- /dev/null +++ b/src/main/java/io/oopy/coding/organization/service/OrganizationService.java @@ -0,0 +1,71 @@ +package io.oopy.coding.organization.service; + +import io.oopy.coding.domain.organization.entity.Organization; +import io.oopy.coding.domain.organization.entity.UserOrganization; +import io.oopy.coding.domain.organization.repository.OrganizationRepository; +import io.oopy.coding.domain.organization.repository.UserOrganizationRepository; +import io.oopy.coding.domain.user.entity.User; +import io.oopy.coding.organization.service.dto.OrganizationDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static java.util.stream.Collectors.toList; + +@Service +@Slf4j +@RequiredArgsConstructor +public class OrganizationService { + + private final UserOrganizationRepository userOrganizationRepository; + private final OrganizationRepository organizationRepository; + + @Transactional + public OrganizationDto.Organization getOrganizationByCode(String code) { + Organization organization = organizationRepository.findByCode(code).orElseThrow(() -> new RuntimeException("없는 조직입니다.")); + return OrganizationDto.Organization.of( + organization.getName(), organization.getCode(), organization.getDescription()); + } + + @Transactional + public List getUserOrganizations(Long userId) { + List userOrganizations = userOrganizationRepository.findByUser(User.builder() + .id(userId).build()); + if (ObjectUtils.isEmpty(userOrganizations)) { + return new ArrayList<>(); + } + List organizations = userOrganizations.stream() + .map(v -> OrganizationDto.UserOrganization.of(v.getOrganization().getName(), + v.getOrganization().getCode(), + v.getOrganization().getDescription(), + v.getEmail() + )) + .collect(toList()); + return organizations; + } + + @Transactional + public void setUserOrganization(OrganizationDto.Cert cert) { + Organization organization = organizationRepository.findByCode(cert.getOrganizationCode()).orElseThrow(()-> new RuntimeException("없는 조직입니다.")); + UserOrganization userOrganization = UserOrganization.of(organization, + User.builder() + .id(cert.getUserId()).build(), + cert.getUserEmail()); + userOrganizationRepository.save(userOrganization); + } + + public boolean isUserAlreadyRegistered(OrganizationDto.Cert cert) { + List userOrganizations = + getUserOrganizations(cert.getUserId()); + Optional organization = userOrganizations.stream() + .filter(v -> v.getCode().equals(cert.getOrganizationCode())) + .findFirst(); + return organization.isEmpty() ? false : true; + } +} diff --git a/src/main/java/io/oopy/coding/organization/service/dto/OrganizationDto.java b/src/main/java/io/oopy/coding/organization/service/dto/OrganizationDto.java new file mode 100644 index 0000000..48b8211 --- /dev/null +++ b/src/main/java/io/oopy/coding/organization/service/dto/OrganizationDto.java @@ -0,0 +1,64 @@ +package io.oopy.coding.organization.service.dto; + +import jakarta.persistence.Column; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.aspectj.weaver.ast.Or; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class OrganizationDto { + + @Getter + public static class Cert { + private String organizationCode; + private Long userId; + private String userEmail; + + public Cert(String organizationCode, Long userId, String userEmail) { + this.organizationCode = organizationCode; + this.userId = userId; + this.userEmail = userEmail; + } + + public static Cert of(String organizationCode, Long userId, String userEmail) { + return new Cert(organizationCode, userId, userEmail); + } + } + + @Getter + public static class Organization { + private String name; + private String code; + private String description; + + public Organization(String name, String code, String description) { + this.name = name; + this.code = code; + this.description = description; + } + + public static Organization of(String name, String code, String description) { + return new Organization(name, code, description); + } + } + + @Getter + public static class UserOrganization { + private String name; + private String code; + private String description; + private String email; + + public UserOrganization(String name, String code, String description, String email) { + this.name = name; + this.code = code; + this.description = description; + this.email = email; + } + + public static UserOrganization of(String name, String code, String description, String email) { + return new UserOrganization(name, code, description, email); + } + } +} From 663a7f23dd01c1dcef5b25a4db549f727cd740fd Mon Sep 17 00:00:00 2001 From: chanho Date: Fri, 22 Sep 2023 21:16:20 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[UPDATE]=20swagger=20operation=20annotati?= =?UTF-8?q?on,=20AuthenticationPrincipal=20annotation=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OrganizationCertificationService.java | 4 +- .../dto/OrganizationCertificationDto.java | 1 - .../controller/EmailCertController.java | 40 +++++++++------- .../emailcert/dto/EmailCertRequest.java | 4 +- .../emailcert/service/EmailCertService.java | 5 -- .../controller/OrganizationController.java | 47 +++++++++++++++---- .../service/OrganizationService.java | 3 +- 7 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java index 5e36076..1e529d8 100644 --- a/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java @@ -69,8 +69,8 @@ public void register(OrganizationCertificationDto.Register register) { ); listOperations.rightPush(register.getKey(), organizationCertification); ZoneId zoneId = ZoneId.systemDefault(); - redisTemplate.expireAt(register.getKey(), organizationCertification.getExpiredAt().atZone(zoneId) - .toInstant()); + redisTemplate.expireAt(register.getKey(), + organizationCertification.getExpiredAt().atZone(zoneId).toInstant()); } @Transactional diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java index be698aa..859725e 100644 --- a/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/dto/OrganizationCertificationDto.java @@ -11,7 +11,6 @@ public class OrganizationCertificationDto { public record Register(String userId, String token, String code, String organizationName, String userEmail) implements OrganizationCertKey { - @Override public String getKey() { return KEY_PREFIX + userId; diff --git a/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java b/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java index a425e87..e581134 100644 --- a/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java +++ b/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java @@ -1,13 +1,16 @@ package io.oopy.coding.emailcert.controller; +import io.oopy.coding.common.security.CustomUserDetails; import io.oopy.coding.emailcert.dto.EmailCertRequest; import io.oopy.coding.emailcert.service.EmailCertService; +import io.swagger.v3.oas.annotations.Operation; import jakarta.mail.MessagingException; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -20,32 +23,33 @@ public class EmailCertController { @Value("${mail.cert.success-redirect-url}") private String successRedirectUrl; - private final EmailCertService emailService; + private final EmailCertService emailCertService; + @Operation(summary = "조직 인증메일 발송", description = + """ + 조직 인증메일을 발송한다. + 해당 메일에서 인증하기 버튼을 누르면, 인증이 되는 식으로 구현. + """ + ) @PostMapping("/send") @ResponseBody - public ResponseEntity sendCertification(@RequestBody EmailCertRequest.Send emailSend - //todo - ,@AuthenticationPrincipal UserDetails userDetails) - )throws MessagingException { - String userId = "1"; - emailService.sendCertMail(emailSend.toCertificateEmailSend(userId)); - return ResponseEntity.noContent().build(); - } - - @DeleteMapping("/cert") - @ResponseBody - public ResponseEntity deleteCert( - @RequestBody EmailCertRequest.Remove cert - //todo - ,@AuthenticationPrincipal UserDetails userDetails) - ){ - String userId = "1"; - emailService.deleteCert(userId, cert.getCode()); + public ResponseEntity sendCertification(@RequestBody EmailCertRequest.Send emailSend, + @AuthenticationPrincipal CustomUserDetails securityUser) throws MessagingException { + emailCertService.sendCertMail(emailSend.toCertificateEmailSend(securityUser.getUserId().toString())); return ResponseEntity.noContent().build(); } + /** + * 메일에서 인증하기 버튼을 눌렀을 때, 이메일 인증 진행 + * @param userId + * @param token + * @param code + * @param httpServletResponse + * @throws Exception + */ @GetMapping("/cert/{userId}/{token}/{code}") public void cert(@PathVariable String userId, @PathVariable String token, @PathVariable String code, HttpServletResponse httpServletResponse) throws Exception{ - emailService.cert(userId, token, code); + emailCertService.cert(userId, token, code); httpServletResponse.sendRedirect(successRedirectUrl); } } diff --git a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java index ef30fcc..0fddc1c 100644 --- a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java +++ b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java @@ -14,9 +14,9 @@ public static class Send { private String targetEmail; private String organizationCode; - public EmailCertificateDto.Send toCertificateEmailSend(String githubId) { + public EmailCertificateDto.Send toCertificateEmailSend(String userId) { return EmailCertificateDto.Send.of(this.targetEmail, - githubId, + userId, CERT_TITLE, null, this.organizationCode diff --git a/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java b/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java index 18681fd..1c2f7f2 100644 --- a/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java +++ b/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java @@ -48,11 +48,6 @@ public void sendCertMail(EmailCertificateDto.Send emailSend) throws MessagingExc emailService.sendMail(emailSend); } - @Transactional - public void deleteCert(String githubId, String code) { - organizationCertificationService.delete(OrganizationCertificationDto.Delete.of(githubId, code)); - } - @Transactional public void cert(String userId, String token, String code) { Optional emailCertification = organizationCertificationService.getOrganizationCertification( diff --git a/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java b/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java index c67cc2f..63cdfa1 100644 --- a/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java +++ b/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java @@ -3,12 +3,15 @@ import io.oopy.coding.common.redis.organizationcert.OrganizationCertification; import io.oopy.coding.common.redis.organizationcert.OrganizationCertificationService; import io.oopy.coding.common.redis.organizationcert.dto.OrganizationCertificationDto; +import io.oopy.coding.common.security.CustomUserDetails; +import io.oopy.coding.emailcert.dto.EmailCertRequest; import io.oopy.coding.organization.dto.OrganizationResponse; import io.oopy.coding.organization.service.OrganizationService; import io.oopy.coding.organization.service.dto.OrganizationDto; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -17,16 +20,19 @@ @RestController @RequestMapping("/organization") @RequiredArgsConstructor -@Slf4j public class OrganizationController { private final OrganizationService organizationService; private final OrganizationCertificationService organizationCertificationService; - @GetMapping("/users/{id}") - public ResponseEntity > getList(@PathVariable Long id) { - id = 1L; - List userOrganizations = organizationService.getUserOrganizations(id); + @Operation(summary = "인증된 조직들 내역 조회", description = + """ + 인증된 조직들 내역을 조회한다. + """ + ) + @GetMapping("/users") + public ResponseEntity > getList(@AuthenticationPrincipal CustomUserDetails securityUser) { + List userOrganizations = organizationService.getUserOrganizations(securityUser.getUserId()); List response = userOrganizations .stream() .map(v->OrganizationResponse.UserOrganization.of(v.getCode(), v.getName(), v.getEmail())) @@ -34,10 +40,14 @@ public ResponseEntity > getList(@Pat return ResponseEntity.ok(response); } - @GetMapping("/pending/users/{id}") - public ResponseEntity > getPendingList(@PathVariable Long id) { - id = 1L; - OrganizationCertificationDto.Key key = OrganizationCertificationDto.Key.of(id.toString()); + @Operation(summary = "인증 진행중인 조직 내역 조회", description = + """ + 인증된 조직들 내역을 조회한다. + """ + ) + @GetMapping("/pending/users") + public ResponseEntity > getPendingList(@AuthenticationPrincipal CustomUserDetails securityUser) { + OrganizationCertificationDto.Key key = OrganizationCertificationDto.Key.of(securityUser.getUserId().toString()); List organizationCertifications = organizationCertificationService.getOrganizationCertifications(key); List response = organizationCertifications .stream() @@ -45,4 +55,21 @@ public ResponseEntity > getPendingLi .collect(Collectors.toList()); return ResponseEntity.ok(response); } + + @Operation(summary = "조직 인증 진행 취소", description = + """ + 조직 인증 진행을 취소한다. + """ + ) + @DeleteMapping("/cert") + public ResponseEntity deleteCert( + @RequestBody EmailCertRequest.Remove cert, + @AuthenticationPrincipal CustomUserDetails securityUser){ + organizationCertificationService.delete( + OrganizationCertificationDto.Delete.of( + securityUser.getUserId().toString(), + cert.getCode() + )); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/io/oopy/coding/organization/service/OrganizationService.java b/src/main/java/io/oopy/coding/organization/service/OrganizationService.java index 7f410d6..c5087e0 100644 --- a/src/main/java/io/oopy/coding/organization/service/OrganizationService.java +++ b/src/main/java/io/oopy/coding/organization/service/OrganizationService.java @@ -55,8 +55,7 @@ public void setUserOrganization(OrganizationDto.Cert cert) { Organization organization = organizationRepository.findByCode(cert.getOrganizationCode()).orElseThrow(()-> new RuntimeException("없는 조직입니다.")); UserOrganization userOrganization = UserOrganization.of(organization, User.builder() - .id(cert.getUserId()).build(), - cert.getUserEmail()); + .id(cert.getUserId()).build(), cert.getUserEmail()); userOrganizationRepository.save(userOrganization); } From daed30e2a481d6be785d1d622879c0237fa74962 Mon Sep 17 00:00:00 2001 From: chanho Date: Thu, 12 Oct 2023 06:26:42 +0900 Subject: [PATCH 08/10] [UPDATE] conflict resolve --- build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.gradle b/build.gradle index cf31bb6..1c58f8a 100644 --- a/build.gradle +++ b/build.gradle @@ -30,13 +30,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'com.jcraft:jsch:0.1.55' -<<<<<<< HEAD implementation 'org.springframework.boot:spring-boot-starter-mail' -======= implementation 'org.springframework.boot:spring-boot-starter-data-redis' ->>>>>>> dev - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' From 9a4a6f753cb862a6920c0604d6b43a8f89bd4756 Mon Sep 17 00:00:00 2001 From: chanho Date: Mon, 11 Dec 2023 20:26:48 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[UPDATE]=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EB=93=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 5 +++-- .../coding/common/constant/EmailConstant.java | 2 +- .../OrganizationCertificationService.java | 21 +++++++++++++++++++ .../filter/JwtAuthenticationFilter.java | 3 +-- .../controller/EmailCertController.java | 7 ++++--- .../emailcert/dto/EmailCertRequest.java | 10 +++------ .../emailcert/dto/EmailCertResponse.java | 4 ---- .../controller/OrganizationController.java | 11 +++++----- 8 files changed, 39 insertions(+), 24 deletions(-) delete mode 100644 src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java diff --git a/src/main/java/io/oopy/coding/common/config/security/SecurityConfig.java b/src/main/java/io/oopy/coding/common/config/security/SecurityConfig.java index bd1d5ec..ced958e 100644 --- a/src/main/java/io/oopy/coding/common/config/security/SecurityConfig.java +++ b/src/main/java/io/oopy/coding/common/config/security/SecurityConfig.java @@ -40,9 +40,10 @@ public class SecurityConfig { "/v3/api-docs/**", "/swagger-ui/**", "/swagger", "/api/v1/users/login", "/api/v1/users/refresh", "/api/v1/auth/login/**", "/api/v1/auth/signup", - "/api/v1/contents/get", "api/v1/comments/get" + "/api/v1/contents/get", "api/v1/comments/get", "/api/v1/profile/**", - "/login/oauth2/**", "/api/v1/feed/title", "/api/v1/feed/body" + "/login/oauth2/**", "/api/v1/feed/title", "/api/v1/feed/body", + "/api/v1/email-cert/cert/**" }; @Bean diff --git a/src/main/java/io/oopy/coding/common/constant/EmailConstant.java b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java index 436436f..ccae864 100644 --- a/src/main/java/io/oopy/coding/common/constant/EmailConstant.java +++ b/src/main/java/io/oopy/coding/common/constant/EmailConstant.java @@ -34,7 +34,7 @@ public class EmailConstant { "\n" + "\n" + "\n" + - "
인증하기
\n" + + "
인증하기
\n" + "\n" + "\n" + ""; diff --git a/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java index 1e529d8..d8cd91e 100644 --- a/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java +++ b/src/main/java/io/oopy/coding/common/redis/organizationcert/OrganizationCertificationService.java @@ -18,6 +18,9 @@ import java.util.Optional; import java.util.stream.Collectors; +/** + * 조직 인증 관련 서비스. Redis를 사용 + */ @Service @Slf4j public class OrganizationCertificationService { @@ -34,6 +37,11 @@ public OrganizationCertificationService(RedisTemplate getOrganizationCertifications(OrganizationKey organizationCertKey) { List organizationCertifications = listOperations.range(organizationCertKey.getKey(), 0, -1); @@ -47,6 +55,10 @@ public List getOrganizationCertifications(Organizatio filteredOrganizationCertifications; } + /** + * 조직 인증 등록. 만료시간 : 600초 + * @param register + */ @Transactional public void register(OrganizationCertificationDto.Register register) { if(organizationService.isUserAlreadyRegistered( @@ -73,6 +85,10 @@ public void register(OrganizationCertificationDto.Register register) { organizationCertification.getExpiredAt().atZone(zoneId).toInstant()); } + /** + * 조직 인증 삭제 + * @param organizationCertKey + */ @Transactional public void delete(OrganizationCertKey organizationCertKey) { Optional organizationCertification = getOrganizationCertification(organizationCertKey); @@ -82,6 +98,11 @@ public void delete(OrganizationCertKey organizationCertKey) { listOperations.remove(organizationCertKey.getKey(), 0, organizationCertification.get()); } + /** + * 진행 중인 조직 정보 리스트 중에, 특정 조직 하나를 뽑아낸다. + * @param cert + * @return + */ public Optional getOrganizationCertification(OrganizationCertKey cert) { List organizationCertificationList = getOrganizationCertifications(cert); if (ObjectUtils.isEmpty(organizationCertificationList)) { diff --git a/src/main/java/io/oopy/coding/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/io/oopy/coding/common/security/filter/JwtAuthenticationFilter.java index 673b17c..2527120 100644 --- a/src/main/java/io/oopy/coding/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/io/oopy/coding/common/security/filter/JwtAuthenticationFilter.java @@ -56,8 +56,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/login/oauth2/**", "/api/v1/contents/get", "/api/v1/comments/get", "/favicon.ico", - "/api/v1/feed/title", "/api/v1/feed/body", - "/email-cert/**", "/organization/**" + "/api/v1/feed/title", "/api/v1/feed/body", "/api/v1/email-cert/cert/**" ); @Override diff --git a/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java b/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java index e581134..33d3da3 100644 --- a/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java +++ b/src/main/java/io/oopy/coding/emailcert/controller/EmailCertController.java @@ -1,11 +1,12 @@ package io.oopy.coding.emailcert.controller; -import io.oopy.coding.common.security.CustomUserDetails; +import io.oopy.coding.common.security.authentication.CustomUserDetails; import io.oopy.coding.emailcert.dto.EmailCertRequest; import io.oopy.coding.emailcert.service.EmailCertService; import io.swagger.v3.oas.annotations.Operation; import jakarta.mail.MessagingException; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -15,7 +16,7 @@ import org.springframework.web.bind.annotation.*; @Controller -@RequestMapping("/email-cert") +@RequestMapping("/api/v1/email-cert") @RequiredArgsConstructor @Slf4j public class EmailCertController { @@ -33,7 +34,7 @@ public class EmailCertController { ) @PostMapping("/send") @ResponseBody - public ResponseEntity sendCertification(@RequestBody EmailCertRequest.Send emailSend, + public ResponseEntity sendCertification(@RequestBody @Valid EmailCertRequest.Send emailSend, @AuthenticationPrincipal CustomUserDetails securityUser) throws MessagingException { emailCertService.sendCertMail(emailSend.toCertificateEmailSend(securityUser.getUserId().toString())); return ResponseEntity.noContent().build(); diff --git a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java index 0fddc1c..9e74dfb 100644 --- a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java +++ b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertRequest.java @@ -1,6 +1,7 @@ package io.oopy.coding.emailcert.dto; import io.oopy.coding.emailcert.service.dto.EmailCertificateDto; +import jakarta.validation.constraints.NotBlank; import lombok.*; import static io.oopy.coding.common.constant.EmailConstant.CERT_TITLE; @@ -10,8 +11,9 @@ public class EmailCertRequest { @Getter public static class Send { - + @NotBlank(message = "이메일은 필수 입력 값입니다.") private String targetEmail; + @NotBlank(message = "조직 코드는 필수로 존재해야 합니다.") private String organizationCode; public EmailCertificateDto.Send toCertificateEmailSend(String userId) { @@ -24,12 +26,6 @@ public EmailCertificateDto.Send toCertificateEmailSend(String userId) { } } - @Getter - public static class Cert { - private String token; - private String code; - } - @Getter public static class Remove { private String code; diff --git a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java b/src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java deleted file mode 100644 index 9c504e1..0000000 --- a/src/main/java/io/oopy/coding/emailcert/dto/EmailCertResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.oopy.coding.emailcert.dto; - -public class EmailCertResponse { -} diff --git a/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java b/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java index 63cdfa1..feec682 100644 --- a/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java +++ b/src/main/java/io/oopy/coding/organization/controller/OrganizationController.java @@ -3,7 +3,8 @@ import io.oopy.coding.common.redis.organizationcert.OrganizationCertification; import io.oopy.coding.common.redis.organizationcert.OrganizationCertificationService; import io.oopy.coding.common.redis.organizationcert.dto.OrganizationCertificationDto; -import io.oopy.coding.common.security.CustomUserDetails; +import io.oopy.coding.common.response.SuccessResponse; +import io.oopy.coding.common.security.authentication.CustomUserDetails; import io.oopy.coding.emailcert.dto.EmailCertRequest; import io.oopy.coding.organization.dto.OrganizationResponse; import io.oopy.coding.organization.service.OrganizationService; @@ -31,13 +32,13 @@ public class OrganizationController { """ ) @GetMapping("/users") - public ResponseEntity > getList(@AuthenticationPrincipal CustomUserDetails securityUser) { + public ResponseEntity > > getList(@AuthenticationPrincipal CustomUserDetails securityUser) { List userOrganizations = organizationService.getUserOrganizations(securityUser.getUserId()); List response = userOrganizations .stream() .map(v->OrganizationResponse.UserOrganization.of(v.getCode(), v.getName(), v.getEmail())) .collect(Collectors.toList()); - return ResponseEntity.ok(response); + return ResponseEntity.ok(SuccessResponse.from(response)); } @Operation(summary = "인증 진행중인 조직 내역 조회", description = @@ -46,14 +47,14 @@ public ResponseEntity > getList(@Aut """ ) @GetMapping("/pending/users") - public ResponseEntity > getPendingList(@AuthenticationPrincipal CustomUserDetails securityUser) { + public ResponseEntity > > getPendingList(@AuthenticationPrincipal CustomUserDetails securityUser) { OrganizationCertificationDto.Key key = OrganizationCertificationDto.Key.of(securityUser.getUserId().toString()); List organizationCertifications = organizationCertificationService.getOrganizationCertifications(key); List response = organizationCertifications .stream() .map(v -> OrganizationResponse.UserOrganization.of(v.getOrganizationCode(), v.getOrganizationName(), v.getUserEmail())) .collect(Collectors.toList()); - return ResponseEntity.ok(response); + return ResponseEntity.ok(SuccessResponse.from(response)); } @Operation(summary = "조직 인증 진행 취소", description = From 4d7cef57df6b96a3139479eda72a1ece18eaac02 Mon Sep 17 00:00:00 2001 From: chanho Date: Mon, 11 Dec 2023 20:43:14 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[UPDATE]=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20-=20GlobalException=EC=9C=BC=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/oopy/coding/common/response/code/ErrorCode.java | 4 ++++ .../oopy/coding/emailcert/service/EmailCertService.java | 8 +++++--- .../coding/organization/dto/OrganizationRequest.java | 9 --------- .../coding/organization/service/OrganizationService.java | 6 ++++-- 4 files changed, 13 insertions(+), 14 deletions(-) delete mode 100644 src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java diff --git a/src/main/java/io/oopy/coding/common/response/code/ErrorCode.java b/src/main/java/io/oopy/coding/common/response/code/ErrorCode.java index ccd4310..9cd361e 100644 --- a/src/main/java/io/oopy/coding/common/response/code/ErrorCode.java +++ b/src/main/java/io/oopy/coding/common/response/code/ErrorCode.java @@ -27,10 +27,12 @@ public enum ErrorCode implements StatusCode { ALREADY_REGISTERED_USER(BAD_REQUEST, "이미 등록된 유저입니다."), ALREADY_LOGIN_USER(BAD_REQUEST, "이미 로그인한 유저입니다."), + ALREADY_ORGANIZATION_CERT(BAD_REQUEST, "이미 인증 진행중인 조직입니다"), EXPIRED_AUTH_CODE(BAD_REQUEST, "인증 시간이 만료되었습니다"), INVALID_AUTH_CODE(BAD_REQUEST, "유효하지 않은 인증 코드입니다"), INVALID_RECEIVER(BAD_REQUEST, "유효하지 않은 수신자입니다"), + INVALID_ORGANIZATION_CERT_TOKEN(BAD_REQUEST, "올바르지 않은 토큰 정보입니다"), /** * 403 FORBIDDEN: 서버에서 요청을 거부한 경우 @@ -44,6 +46,8 @@ public enum ErrorCode implements StatusCode { NULL_POINT_ERROR(NOT_FOUND,"Null Point Exception"), NOT_VALID_ERROR(NOT_FOUND,"유효하지 않은 요청입니다."), NOT_VALID_HEADER_ERROR(NOT_FOUND,"헤더에 데이터가 존재하지 않습니다."), + NOT_FOUND_ORGANIZATION(NOT_FOUND, "존재하지 않는 조직입니다."), + NOT_FOUND_ORGANIZATION_CERT(NOT_FOUND, "진행중인 인증이 존재하지 않습니다."), /** * 500 INTERNAL_SERVER_ERROR: 서버에서 에러가 발생한 경우 diff --git a/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java b/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java index 1c2f7f2..eb66d83 100644 --- a/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java +++ b/src/main/java/io/oopy/coding/emailcert/service/EmailCertService.java @@ -4,6 +4,8 @@ import io.oopy.coding.common.redis.organizationcert.OrganizationCertification; import io.oopy.coding.common.redis.organizationcert.OrganizationCertificationService; import io.oopy.coding.common.redis.organizationcert.dto.OrganizationCertificationDto; +import io.oopy.coding.common.response.code.ErrorCode; +import io.oopy.coding.common.response.exception.GlobalErrorException; import io.oopy.coding.emailcert.service.dto.EmailCertificateDto; import io.oopy.coding.organization.service.OrganizationService; import io.oopy.coding.organization.service.dto.OrganizationDto; @@ -53,10 +55,10 @@ public void cert(String userId, String token, String code) { Optional emailCertification = organizationCertificationService.getOrganizationCertification( OrganizationCertificationDto.Cert.of(userId, token, code)); if (emailCertification.isEmpty()) { - throw new RuntimeException("진행중이지 않습니다."); + throw new GlobalErrorException(ErrorCode.NOT_FOUND_ORGANIZATION_CERT); } if (!token.equals(emailCertification.get().getCertToken())) { - throw new RuntimeException("토큰 정보가 올바르지 않습니다."); + throw new GlobalErrorException(ErrorCode.INVALID_ORGANIZATION_CERT_TOKEN); } organizationService.setUserOrganization( OrganizationDto.Cert.of(code, Long.parseLong(userId), emailCertification.get().getUserEmail())); @@ -69,7 +71,7 @@ private void checkSendValid(EmailCertificateDto.Send emailSend) { Optional emailCertificationBySet = organizationCertificationService.getOrganizationCertification( OrganizationCertificationDto.Get.of(emailSend.getUserId(), emailSend.getOrganizationCode())); if (emailCertificationBySet.isPresent()) { - throw new RuntimeException("인증 진행중"); + throw new GlobalErrorException(ErrorCode.ALREADY_ORGANIZATION_CERT); } } } diff --git a/src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java b/src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java deleted file mode 100644 index 62a38ff..0000000 --- a/src/main/java/io/oopy/coding/organization/dto/OrganizationRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.oopy.coding.organization.dto; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class OrganizationRequest { -} diff --git a/src/main/java/io/oopy/coding/organization/service/OrganizationService.java b/src/main/java/io/oopy/coding/organization/service/OrganizationService.java index c5087e0..c5f68a1 100644 --- a/src/main/java/io/oopy/coding/organization/service/OrganizationService.java +++ b/src/main/java/io/oopy/coding/organization/service/OrganizationService.java @@ -1,5 +1,7 @@ package io.oopy.coding.organization.service; +import io.oopy.coding.common.response.code.ErrorCode; +import io.oopy.coding.common.response.exception.GlobalErrorException; import io.oopy.coding.domain.organization.entity.Organization; import io.oopy.coding.domain.organization.entity.UserOrganization; import io.oopy.coding.domain.organization.repository.OrganizationRepository; @@ -28,7 +30,7 @@ public class OrganizationService { @Transactional public OrganizationDto.Organization getOrganizationByCode(String code) { - Organization organization = organizationRepository.findByCode(code).orElseThrow(() -> new RuntimeException("없는 조직입니다.")); + Organization organization = organizationRepository.findByCode(code).orElseThrow(() -> new GlobalErrorException(ErrorCode.NOT_FOUND_ORGANIZATION)); return OrganizationDto.Organization.of( organization.getName(), organization.getCode(), organization.getDescription()); } @@ -52,7 +54,7 @@ public List getUserOrganizations(Long userId) @Transactional public void setUserOrganization(OrganizationDto.Cert cert) { - Organization organization = organizationRepository.findByCode(cert.getOrganizationCode()).orElseThrow(()-> new RuntimeException("없는 조직입니다.")); + Organization organization = organizationRepository.findByCode(cert.getOrganizationCode()).orElseThrow(()-> new GlobalErrorException(ErrorCode.NOT_FOUND_ORGANIZATION)); UserOrganization userOrganization = UserOrganization.of(organization, User.builder() .id(cert.getUserId()).build(), cert.getUserEmail());