From 4d6fd499ae9e6a6212df173ee2ab6761c83bcc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Sun, 16 Jun 2024 23:55:16 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20&=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EmailVerificationController.java | 71 +++++++++++++++++++ .../EmailVerificationCodeRequestDTO.java | 20 ++++++ .../request/EmailVerificationRequestDTO.java | 21 ++++++ .../email/entity/EmailVerifications.java | 47 ++++++++++++ .../domain/email/enums/UserType.java | 6 ++ .../email/enums/VerificationStatus.java | 6 ++ .../EmailVerificationRepository.java | 13 ++++ .../domain/email/service/EmailService.java | 6 ++ .../email/service/EmailServiceImpl.java | 24 +++++++ .../service/EmailVerificationService.java | 55 ++++++++++++++ .../global/config/MailConfig.java | 42 +++++++++++ .../global/config/WebSecurityConfig.java | 1 + .../global/exception/ErrorCode.java | 3 + .../global/response/SuccessCode.java | 4 ++ src/main/resources/application-local.yml | 12 ++++ src/main/resources/application-prod.yml | 18 +++++ 16 files changed, 349 insertions(+) create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationCodeRequestDTO.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationRequestDTO.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/enums/UserType.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/enums/VerificationStatus.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/service/EmailService.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/service/EmailServiceImpl.java create mode 100644 src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java create mode 100644 src/main/java/team9502/sinchulgwinong/global/config/MailConfig.java diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java b/src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java new file mode 100644 index 0000000..1f28abe --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java @@ -0,0 +1,71 @@ +package team9502.sinchulgwinong.domain.email.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +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 team9502.sinchulgwinong.domain.email.dto.request.EmailVerificationCodeRequestDTO; +import team9502.sinchulgwinong.domain.email.dto.request.EmailVerificationRequestDTO; +import team9502.sinchulgwinong.domain.email.service.EmailVerificationService; +import team9502.sinchulgwinong.global.response.GlobalApiResponse; + +import static team9502.sinchulgwinong.global.response.SuccessCode.SUCCESS_EMAIL_VERIFICATION; +import static team9502.sinchulgwinong.global.response.SuccessCode.SUCCESS_EMAIL_VERIFICATION_SENT; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/email") +@Tag(name = "Email Verification", description = "이메일 인증 관련 API [김은채]") +public class EmailVerificationController { + + private final EmailVerificationService emailVerificationService; + + @PostMapping("/sendCode") + @Operation(summary = "이메일 인증 코드 발송", description = "사용자 이메일로 인증 코드를 발송합니다.") + @ApiResponse(responseCode = "200", description = "인증 코드 발송 성공", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"200\", \"message\": \"인증 코드 발송 성공\", \"data\": null }"))) + public ResponseEntity> sendVerificationCode( + @RequestBody EmailVerificationRequestDTO requestDTO) { + + emailVerificationService.createVerification(requestDTO.getEmail(), requestDTO.getUserType()); + + return ResponseEntity.status(SUCCESS_EMAIL_VERIFICATION_SENT.getHttpStatus()) + .body( + GlobalApiResponse.of( + SUCCESS_EMAIL_VERIFICATION_SENT.getMessage(), + null + ) + ); + } + + @PostMapping("/verifyCode") + @Operation(summary = "이메일 인증 코드 검증", description = "제공된 이메일과 인증 코드의 유효성을 검증합니다.") + @ApiResponse(responseCode = "200", description = "이메일 인증 성공", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"200\", \"message\": \"이메일 인증 성공\", \"data\": null }"))) + @ApiResponse(responseCode = "400", description = "유효하지 않거나 만료된 코드", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"유효하지 않거나 만료된 인증 코드\", \"data\": null }"))) + public ResponseEntity> verifyCode( + @RequestBody EmailVerificationCodeRequestDTO requestDTO) { + + emailVerificationService.verifyCode(requestDTO.getEmail(), requestDTO.getCode()); + + + return ResponseEntity.status(SUCCESS_EMAIL_VERIFICATION.getHttpStatus()) + .body( + GlobalApiResponse.of( + SUCCESS_EMAIL_VERIFICATION.getMessage(), + null + ) + ); + } +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationCodeRequestDTO.java b/src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationCodeRequestDTO.java new file mode 100644 index 0000000..03ecac1 --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationCodeRequestDTO.java @@ -0,0 +1,20 @@ +package team9502.sinchulgwinong.domain.email.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Data +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class EmailVerificationCodeRequestDTO { + + @Schema(description = "이메일 주소", example = "example@email.com") + private String email; + + @Schema(description = "인증 코드", example = "123456") + private String code; +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationRequestDTO.java b/src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationRequestDTO.java new file mode 100644 index 0000000..d9aa4ae --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/dto/request/EmailVerificationRequestDTO.java @@ -0,0 +1,21 @@ +package team9502.sinchulgwinong.domain.email.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import team9502.sinchulgwinong.domain.email.enums.UserType; + +@Data +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class EmailVerificationRequestDTO { + + @Schema(description = "이메일 주소", example = "example@email.com") + private String email; + + @Schema(description = "사용자 유형", example = "USER") + private UserType userType; +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java b/src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java new file mode 100644 index 0000000..b7fe91c --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java @@ -0,0 +1,47 @@ +package team9502.sinchulgwinong.domain.email.entity; + +import jakarta.persistence.*; +import lombok.*; +import team9502.sinchulgwinong.domain.email.enums.UserType; +import team9502.sinchulgwinong.domain.email.enums.VerificationStatus; + +import java.util.Date; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "EmailVerifications") +public class EmailVerifications { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long verificationId; + + @Column(nullable = false, length = 250) + private String email; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private UserType userType; + + @Column(nullable = true) + private Long userReferenceId; + + @Column(nullable = false, length = 6) + private String verificationCode; + + @Column(nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date createdAt; + + @Column(nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date expiresAt; + + @Setter + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private VerificationStatus status; +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/enums/UserType.java b/src/main/java/team9502/sinchulgwinong/domain/email/enums/UserType.java new file mode 100644 index 0000000..96b8afb --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/enums/UserType.java @@ -0,0 +1,6 @@ +package team9502.sinchulgwinong.domain.email.enums; + +public enum UserType { + + USER, CPUSER +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/enums/VerificationStatus.java b/src/main/java/team9502/sinchulgwinong/domain/email/enums/VerificationStatus.java new file mode 100644 index 0000000..bc83848 --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/enums/VerificationStatus.java @@ -0,0 +1,6 @@ +package team9502.sinchulgwinong.domain.email.enums; + +public enum VerificationStatus { + + PENDING, VERIFIED, EXPIRED +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java b/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java new file mode 100644 index 0000000..66aa9ca --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java @@ -0,0 +1,13 @@ +package team9502.sinchulgwinong.domain.email.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import team9502.sinchulgwinong.domain.email.entity.EmailVerifications; +import team9502.sinchulgwinong.domain.email.enums.VerificationStatus; + +import java.util.Date; +import java.util.Optional; + +public interface EmailVerificationRepository extends JpaRepository { + + Optional findByEmailAndVerificationCodeAndExpiresAtAfterAndStatus(String email, String verificationCode, Date now, VerificationStatus status); +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailService.java b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailService.java new file mode 100644 index 0000000..53fbb1f --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailService.java @@ -0,0 +1,6 @@ +package team9502.sinchulgwinong.domain.email.service; + +public interface EmailService { + + void sendSimpleMessage(String to, String subject, String text); +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailServiceImpl.java b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailServiceImpl.java new file mode 100644 index 0000000..b9549d8 --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailServiceImpl.java @@ -0,0 +1,24 @@ +package team9502.sinchulgwinong.domain.email.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class EmailServiceImpl implements EmailService { + + private final JavaMailSender mailSender; + + @Override + public void sendSimpleMessage(String to, String subject, String text) { + + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom("${GOOGLE_ACCOUNT}"); + message.setTo(to); + message.setSubject(subject); + message.setText(text); + mailSender.send(message); + } +} diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java new file mode 100644 index 0000000..9ab71c5 --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java @@ -0,0 +1,55 @@ +package team9502.sinchulgwinong.domain.email.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import team9502.sinchulgwinong.domain.email.entity.EmailVerifications; +import team9502.sinchulgwinong.domain.email.enums.UserType; +import team9502.sinchulgwinong.domain.email.enums.VerificationStatus; +import team9502.sinchulgwinong.domain.email.repository.EmailVerificationRepository; +import team9502.sinchulgwinong.global.exception.ApiException; +import team9502.sinchulgwinong.global.exception.ErrorCode; + +import java.util.Date; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class EmailVerificationService { + + private final EmailVerificationRepository emailVerificationRepository; + private final EmailService emailService; + + @Transactional + public void createVerification(String email, UserType userType) { + + String code = generateVerificationCode(); + EmailVerifications verification = EmailVerifications.builder() + .email(email) + .userType(userType) + .verificationCode(code) + .createdAt(new Date()) + .expiresAt(new Date(System.currentTimeMillis() + 5 * 60 * 1000)) // 5분 + .status(VerificationStatus.PENDING) + .build(); + emailVerificationRepository.save(verification); + + emailService.sendSimpleMessage(email, "[신출귀농] 이메일 인증 코드", "인증 코드: " + code); + } + + @Transactional + public void verifyCode(String email, String code) { + + EmailVerifications verification = emailVerificationRepository.findByEmailAndVerificationCodeAndExpiresAtAfterAndStatus( + email, code, new Date(), VerificationStatus.PENDING) + .orElseThrow(() -> new ApiException(ErrorCode.INVALID_OR_EXPIRED_VERIFICATION_CODE)); + + verification.setStatus(VerificationStatus.VERIFIED); + emailVerificationRepository.save(verification); + } + + private String generateVerificationCode() { + + return UUID.randomUUID().toString().substring(0, 6); + } +} diff --git a/src/main/java/team9502/sinchulgwinong/global/config/MailConfig.java b/src/main/java/team9502/sinchulgwinong/global/config/MailConfig.java new file mode 100644 index 0000000..1c8c361 --- /dev/null +++ b/src/main/java/team9502/sinchulgwinong/global/config/MailConfig.java @@ -0,0 +1,42 @@ +package team9502.sinchulgwinong.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfig { + + @Value("${spring.mail.host}") + private String mailHost; + + @Value("${spring.mail.port}") + private int mailPort; + + @Value("${spring.mail.username}") + private String mailUsername; + + @Value("${spring.mail.password}") + private String mailPassword; + + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(mailHost); + mailSender.setPort(mailPort); + mailSender.setUsername(mailUsername); + mailSender.setPassword(mailPassword); + + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.debug", "true"); + + return mailSender; + } +} diff --git a/src/main/java/team9502/sinchulgwinong/global/config/WebSecurityConfig.java b/src/main/java/team9502/sinchulgwinong/global/config/WebSecurityConfig.java index e591751..aac915f 100644 --- a/src/main/java/team9502/sinchulgwinong/global/config/WebSecurityConfig.java +++ b/src/main/java/team9502/sinchulgwinong/global/config/WebSecurityConfig.java @@ -33,6 +33,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .authorizeHttpRequests(auth -> auth .requestMatchers("/", "/home", "/aws", "/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**").permitAll() .requestMatchers("/auth/signup", "/auth/login", "/auth/cp-signup", "/auth/cp-login").permitAll() + .requestMatchers("/email/**").permitAll() .requestMatchers("/business/status", "/business/verify").permitAll() .requestMatchers(HttpMethod.GET, "/jobBoards", "/jobBoards/{jobBoardId}").permitAll() .anyRequest().authenticated()) diff --git a/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java b/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java index fbb19d6..c757cd1 100644 --- a/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java +++ b/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java @@ -22,6 +22,9 @@ public enum ErrorCode { INVALID_LOGIN_TYPE(HttpStatus.BAD_REQUEST, "잘못된 로그인 유형입니다."), PASSWORD_CONFIRM_MISMATCH(HttpStatus.BAD_REQUEST, "비밀번호와 비밀번호 확인이 일치하지 않습니다."), + // Email + INVALID_OR_EXPIRED_VERIFICATION_CODE(HttpStatus.BAD_REQUEST, "유효하지 않거나 만료된 인증 코드입니다."), + //Board BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."), TITLE_TOO_LONG(HttpStatus.BAD_REQUEST, "제목은 100자를 초과할 수 없습니다."), diff --git a/src/main/java/team9502/sinchulgwinong/global/response/SuccessCode.java b/src/main/java/team9502/sinchulgwinong/global/response/SuccessCode.java index 31158b6..67b68dc 100644 --- a/src/main/java/team9502/sinchulgwinong/global/response/SuccessCode.java +++ b/src/main/java/team9502/sinchulgwinong/global/response/SuccessCode.java @@ -19,6 +19,10 @@ public enum SuccessCode { SUCCESS_USER_PROFILE_UPDATED(HttpStatus.OK, "구직자 프로필 수정 성공"), SUCCESS_USER_PASSWORD_UPDATED(HttpStatus.OK, "구직자 비밀번호 수정 성공"), + // Email + SUCCESS_EMAIL_VERIFICATION_SENT(HttpStatus.OK, "이메일 인증 코드 발송 성공"), + SUCCESS_EMAIL_VERIFICATION(HttpStatus.OK, "이메일 인증 성공"), + // CompanyUser SUCCESS_CP_USER_PROFILE_READ(HttpStatus.OK, "기업(회원) 프로필 조회 성공"), SUCCESS_CP_USER_PROFILE_UPDATED(HttpStatus.OK, "기업(회원) 프로필 수정 성공"), diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index db92b9e..eca4106 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -22,6 +22,18 @@ spring: swagger-ui: path: /swagger-ui.html + mail: + host: smtp.gmail.com + port: 587 + username: ${GOOGLE_ACCOUNT} + password: ${GOOGLE_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + jwt: secret: ${jwt.secretKey} expirationMs: 21600000 # 6 hours diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 0c0510e..71e5b77 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -15,6 +15,24 @@ spring: dialect: org.hibernate.dialect.MySQLDialect show-sql: true + doc: + api-docs: + path: /v3/api-docs + swagger-ui: + path: /swagger-ui.html + + mail: + host: smtp.gmail.com + port: 587 + username: ${GOOGLE_ACCOUNT} + password: ${GOOGLE_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + jwt: secret: ${JWT_SECRETKEY} expirationMs: 3600000 # 1 hour From 621e7fde4f65888c60cfd8060b3befe36fde586a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 00:04:57 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EmailVerificationRepository.java | 5 ++++ .../service/EmailVerificationService.java | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java b/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java index 66aa9ca..3c6de92 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java +++ b/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java @@ -5,9 +5,14 @@ import team9502.sinchulgwinong.domain.email.enums.VerificationStatus; import java.util.Date; +import java.util.List; import java.util.Optional; public interface EmailVerificationRepository extends JpaRepository { Optional findByEmailAndVerificationCodeAndExpiresAtAfterAndStatus(String email, String verificationCode, Date now, VerificationStatus status); + + List findByExpiresAtBeforeAndStatus(Date expiresAt, VerificationStatus status); + + void deleteByExpiresAtBeforeAndStatus(Date expiresAt, VerificationStatus status); } diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java index 9ab71c5..f16cc0f 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java +++ b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java @@ -1,6 +1,7 @@ package team9502.sinchulgwinong.domain.email.service; import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import team9502.sinchulgwinong.domain.email.entity.EmailVerifications; @@ -11,6 +12,7 @@ import team9502.sinchulgwinong.global.exception.ErrorCode; import java.util.Date; +import java.util.List; import java.util.UUID; @Service @@ -48,6 +50,32 @@ email, code, new Date(), VerificationStatus.PENDING) emailVerificationRepository.save(verification); } + @Scheduled(cron = "0 0 * * * *") // 1시간마다 실행 + @Transactional + public void handleExpiredVerifications() { + + updateExpiredVerifications(); // 만료된 인증 코드를 EXPIRED 상태로 업데이트 + deleteExpiredVerifications(); // EXPIRED 상태의 인증 코드를 삭제 + } + + @Transactional + public void updateExpiredVerifications() { + + Date now = new Date(); + List expiredVerifications = emailVerificationRepository.findByExpiresAtBeforeAndStatus(now, VerificationStatus.PENDING); + for (EmailVerifications verification : expiredVerifications) { + verification.setStatus(VerificationStatus.EXPIRED); + } + emailVerificationRepository.saveAll(expiredVerifications); + } + + @Transactional + public void deleteExpiredVerifications() { + + Date now = new Date(); + emailVerificationRepository.deleteByExpiresAtBeforeAndStatus(now, VerificationStatus.EXPIRED); + } + private String generateVerificationCode() { return UUID.randomUUID().toString().substring(0, 6); From 0090cbbe6b09c39909570c823505c396ef049c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 00:10:34 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/email/entity/EmailVerifications.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java b/src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java index b7fe91c..c834000 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java +++ b/src/main/java/team9502/sinchulgwinong/domain/email/entity/EmailVerifications.java @@ -12,7 +12,9 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Table(name = "EmailVerifications") +@Table(name = "EmailVerifications", indexes = { + @Index(name = "idx_expires_at_status", columnList = "expiresAt, status") +}) public class EmailVerifications { @Id @@ -26,7 +28,7 @@ public class EmailVerifications { @Enumerated(EnumType.STRING) private UserType userType; - @Column(nullable = true) + @Column private Long userReferenceId; @Column(nullable = false, length = 6) From 8b6bb3d52e7f192db7add97777ac25cc1e3b9205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 00:30:02 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/EmailVerificationService.java | 106 ++++++++++++++---- .../global/exception/ErrorCode.java | 12 +- 2 files changed, 90 insertions(+), 28 deletions(-) diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java index f16cc0f..313fc60 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java +++ b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java @@ -1,6 +1,8 @@ package team9502.sinchulgwinong.domain.email.service; import lombok.RequiredArgsConstructor; +import org.springframework.mail.MailAuthenticationException; +import org.springframework.mail.MailException; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,59 +27,115 @@ public class EmailVerificationService { @Transactional public void createVerification(String email, UserType userType) { - String code = generateVerificationCode(); - EmailVerifications verification = EmailVerifications.builder() - .email(email) - .userType(userType) - .verificationCode(code) - .createdAt(new Date()) - .expiresAt(new Date(System.currentTimeMillis() + 5 * 60 * 1000)) // 5분 - .status(VerificationStatus.PENDING) - .build(); - emailVerificationRepository.save(verification); + validateEmail(email); - emailService.sendSimpleMessage(email, "[신출귀농] 이메일 인증 코드", "인증 코드: " + code); + try { + String code = generateVerificationCode(); + EmailVerifications verification = buildVerification(email, userType, code); + emailVerificationRepository.save(verification); + + emailService.sendSimpleMessage(email, "[신출귀농] 이메일 인증 코드", "인증 코드: " + code); + } catch (MailAuthenticationException exception) { + throw new ApiException(ErrorCode.EMAIL_AUTH_FAILED); + } catch (MailException exception) { + throw new ApiException(ErrorCode.EMAIL_SEND_FAILED); + } catch (Exception exception) { + throw new ApiException(ErrorCode.INTERNAL_SERVER_ERROR); + } } @Transactional public void verifyCode(String email, String code) { - EmailVerifications verification = emailVerificationRepository.findByEmailAndVerificationCodeAndExpiresAtAfterAndStatus( - email, code, new Date(), VerificationStatus.PENDING) - .orElseThrow(() -> new ApiException(ErrorCode.INVALID_OR_EXPIRED_VERIFICATION_CODE)); - - verification.setStatus(VerificationStatus.VERIFIED); - emailVerificationRepository.save(verification); + validateEmailAndCode(email, code); + + try { + EmailVerifications verification = findPendingVerification(email, code); + if (verification.getExpiresAt().before(new Date())) { + throw new ApiException(ErrorCode.EXPIRED_VERIFICATION_CODE); + } + verification.setStatus(VerificationStatus.VERIFIED); + emailVerificationRepository.save(verification); + } catch (ApiException exception) { + throw exception; + } catch (Exception exception) { + throw new ApiException(ErrorCode.INTERNAL_SERVER_ERROR); + } } @Scheduled(cron = "0 0 * * * *") // 1시간마다 실행 @Transactional public void handleExpiredVerifications() { - updateExpiredVerifications(); // 만료된 인증 코드를 EXPIRED 상태로 업데이트 - deleteExpiredVerifications(); // EXPIRED 상태의 인증 코드를 삭제 + try { + updateExpiredVerifications(); + deleteExpiredVerifications(); + } catch (Exception exception) { + throw new ApiException(ErrorCode.INTERNAL_SERVER_ERROR); + } } @Transactional public void updateExpiredVerifications() { Date now = new Date(); - List expiredVerifications = emailVerificationRepository.findByExpiresAtBeforeAndStatus(now, VerificationStatus.PENDING); - for (EmailVerifications verification : expiredVerifications) { - verification.setStatus(VerificationStatus.EXPIRED); + try { + List expiredVerifications = emailVerificationRepository.findByExpiresAtBeforeAndStatus(now, VerificationStatus.PENDING); + for (EmailVerifications verification : expiredVerifications) { + verification.setStatus(VerificationStatus.EXPIRED); + } + emailVerificationRepository.saveAll(expiredVerifications); + } catch (Exception exception) { + throw new ApiException(ErrorCode.INTERNAL_SERVER_ERROR); } - emailVerificationRepository.saveAll(expiredVerifications); } @Transactional public void deleteExpiredVerifications() { Date now = new Date(); - emailVerificationRepository.deleteByExpiresAtBeforeAndStatus(now, VerificationStatus.EXPIRED); + try { + emailVerificationRepository.deleteByExpiresAtBeforeAndStatus(now, VerificationStatus.EXPIRED); + } catch (Exception exception) { + throw new ApiException(ErrorCode.INTERNAL_SERVER_ERROR); + } } private String generateVerificationCode() { return UUID.randomUUID().toString().substring(0, 6); } + + private void validateEmail(String email) { + + if (email == null || email.isEmpty()) { + throw new ApiException(ErrorCode.INVALID_INPUT); + } + } + + private void validateEmailAndCode(String email, String code) { + + if (email == null || email.isEmpty() || code == null || code.isEmpty()) { + throw new ApiException(ErrorCode.INVALID_INPUT); + } + } + + private EmailVerifications findPendingVerification(String email, String code) { + + return emailVerificationRepository.findByEmailAndVerificationCodeAndExpiresAtAfterAndStatus( + email, code, new Date(), VerificationStatus.PENDING) + .orElseThrow(() -> new ApiException(ErrorCode.INVALID_VERIFICATION_CODE)); + } + + private EmailVerifications buildVerification(String email, UserType userType, String code) { + + return EmailVerifications.builder() + .email(email) + .userType(userType) + .verificationCode(code) + .createdAt(new Date()) + .expiresAt(new Date(System.currentTimeMillis() + 5 * 60 * 1000)) // 5분 + .status(VerificationStatus.PENDING) + .build(); + } } diff --git a/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java b/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java index c757cd1..a8235fc 100644 --- a/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java +++ b/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java @@ -10,6 +10,9 @@ public enum ErrorCode { //공통 FORBIDDEN_WORK(HttpStatus.UNAUTHORIZED, "이 작업을 수행할 권한이 없습니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러"), + VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "유효성 검사 실패"), + INVALID_INPUT(HttpStatus.BAD_REQUEST, "잘못된 입력입니다."), // User & Auth REQUIRED_ADMIN_USER_AUTHORITY(HttpStatus.UNAUTHORIZED, "관리자 권한이 필요합니다."), @@ -23,7 +26,10 @@ public enum ErrorCode { PASSWORD_CONFIRM_MISMATCH(HttpStatus.BAD_REQUEST, "비밀번호와 비밀번호 확인이 일치하지 않습니다."), // Email - INVALID_OR_EXPIRED_VERIFICATION_CODE(HttpStatus.BAD_REQUEST, "유효하지 않거나 만료된 인증 코드입니다."), + EMAIL_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "이메일 인증에 실패했습니다."), + EMAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "이메일 전송에 실패했습니다."), + EXPIRED_VERIFICATION_CODE(HttpStatus.BAD_REQUEST, "만료된 인증 코드입니다."), + INVALID_VERIFICATION_CODE(HttpStatus.BAD_REQUEST, "유효하지 않은 인증 코드입니다."), //Board BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."), @@ -35,7 +41,7 @@ public enum ErrorCode { // Point POINT_NOT_FOUND(HttpStatus.NOT_FOUND, "포인트를 찾을 수 없습니다."), INSUFFICIENT_POINTS(HttpStatus.BAD_REQUEST, "포인트가 부족합니다."), - + //Comment COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."), @@ -53,9 +59,7 @@ public enum ErrorCode { REVIEW_ALREADY_PUBLIC(HttpStatus.BAD_REQUEST, "이미 공개된 리뷰입니다."), // Global - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 에러"), INTERNAL_BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), - VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "유효성 검사 실패"), INVALID_USER_TYPE(HttpStatus.BAD_REQUEST, "잘못된 사용자 유형입니다."); From f1bfdc7eff7a4a5b015721fe14d725c545357b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 00:30:16 +0900 Subject: [PATCH 05/10] =?UTF-8?q?fix:=20swagger=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/EmailVerificationController.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java b/src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java index 1f28abe..7294322 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java +++ b/src/main/java/team9502/sinchulgwinong/domain/email/controller/EmailVerificationController.java @@ -32,6 +32,15 @@ public class EmailVerificationController { @ApiResponse(responseCode = "200", description = "인증 코드 발송 성공", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"200\", \"message\": \"인증 코드 발송 성공\", \"data\": null }"))) + @ApiResponse(responseCode = "400", description = "잘못된 입력", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"잘못된 입력입니다.\", \"data\": null }"))) + @ApiResponse(responseCode = "401", description = "이메일 인증 실패", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"401\", \"message\": \"이메일 인증에 실패했습니다.\", \"data\": null }"))) + @ApiResponse(responseCode = "500", description = "서버 에러", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"500\", \"message\": \"서버 에러\", \"data\": null }"))) public ResponseEntity> sendVerificationCode( @RequestBody EmailVerificationRequestDTO requestDTO) { @@ -54,6 +63,12 @@ public ResponseEntity> sendVerificationCode( @ApiResponse(responseCode = "400", description = "유효하지 않거나 만료된 코드", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"유효하지 않거나 만료된 인증 코드\", \"data\": null }"))) + @ApiResponse(responseCode = "401", description = "잘못된 입력", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"잘못된 입력입니다.\", \"data\": null }"))) + @ApiResponse(responseCode = "500", description = "서버 에러", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"500\", \"message\": \"서버 에러\", \"data\": null }"))) public ResponseEntity> verifyCode( @RequestBody EmailVerificationCodeRequestDTO requestDTO) { From c1648126e50b884156eb8b2084a68ce3c739437c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 00:55:44 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=EC=8B=9C=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthService.java | 12 ++++++++++++ .../repository/EmailVerificationRepository.java | 2 ++ .../email/service/EmailVerificationService.java | 6 ++++++ .../sinchulgwinong/global/exception/ErrorCode.java | 1 + 4 files changed, 21 insertions(+) diff --git a/src/main/java/team9502/sinchulgwinong/domain/auth/service/AuthService.java b/src/main/java/team9502/sinchulgwinong/domain/auth/service/AuthService.java index a2f8654..09ca4ed 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/auth/service/AuthService.java +++ b/src/main/java/team9502/sinchulgwinong/domain/auth/service/AuthService.java @@ -18,6 +18,7 @@ import team9502.sinchulgwinong.domain.companyUser.entity.CompanyUser; import team9502.sinchulgwinong.domain.companyUser.repository.CompanyUserRepository; import team9502.sinchulgwinong.domain.companyUser.service.EncryptionService; +import team9502.sinchulgwinong.domain.email.service.EmailVerificationService; import team9502.sinchulgwinong.domain.point.enums.SpType; import team9502.sinchulgwinong.domain.point.service.PointService; import team9502.sinchulgwinong.domain.user.entity.User; @@ -40,12 +41,18 @@ public class AuthService { private final AuthenticationManager authenticationManager; private final JwtTokenProvider jwtTokenProvider; private final PointService pointService; + private final EmailVerificationService emailVerificationService; @Transactional public void signup(UserSignupRequestDTO signupRequest) { + validateUserSignupRequest(signupRequest.getEmail(), signupRequest.getPassword(), signupRequest.getConfirmPassword(), signupRequest.isAgreeToTerms()); + if (!emailVerificationService.isEmailVerified(signupRequest.getEmail())) { + throw new ApiException(ErrorCode.EMAIL_NOT_VERIFIED); + } + try { User user = User.builder() .username(signupRequest.getUsername()) @@ -66,9 +73,14 @@ public void signup(UserSignupRequestDTO signupRequest) { @Transactional public void cpSignup(CpUserSignupRequestDTO requestDTO) { + validateCpSignupRequest(requestDTO.getCpEmail(), requestDTO.getCpPassword(), requestDTO.getCpConfirmPassword(), requestDTO.isAgreeToTerms()); + if (!emailVerificationService.isEmailVerified(requestDTO.getCpEmail())) { + throw new ApiException(ErrorCode.EMAIL_NOT_VERIFIED); + } + try { CompanyUser companyUser = CompanyUser.builder() .hiringStatus(requestDTO.getHiringStatus()) diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java b/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java index 3c6de92..61c9379 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java +++ b/src/main/java/team9502/sinchulgwinong/domain/email/repository/EmailVerificationRepository.java @@ -15,4 +15,6 @@ public interface EmailVerificationRepository extends JpaRepository findByExpiresAtBeforeAndStatus(Date expiresAt, VerificationStatus status); void deleteByExpiresAtBeforeAndStatus(Date expiresAt, VerificationStatus status); + + boolean existsByEmailAndStatus(String email, VerificationStatus status); } diff --git a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java index 313fc60..a2c6f9f 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java +++ b/src/main/java/team9502/sinchulgwinong/domain/email/service/EmailVerificationService.java @@ -63,6 +63,12 @@ public void verifyCode(String email, String code) { } } + @Transactional(readOnly = true) + public boolean isEmailVerified(String email) { + + return emailVerificationRepository.existsByEmailAndStatus(email, VerificationStatus.VERIFIED); + } + @Scheduled(cron = "0 0 * * * *") // 1시간마다 실행 @Transactional public void handleExpiredVerifications() { diff --git a/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java b/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java index a8235fc..e2e49ba 100644 --- a/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java +++ b/src/main/java/team9502/sinchulgwinong/global/exception/ErrorCode.java @@ -30,6 +30,7 @@ public enum ErrorCode { EMAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "이메일 전송에 실패했습니다."), EXPIRED_VERIFICATION_CODE(HttpStatus.BAD_REQUEST, "만료된 인증 코드입니다."), INVALID_VERIFICATION_CODE(HttpStatus.BAD_REQUEST, "유효하지 않은 인증 코드입니다."), + EMAIL_NOT_VERIFIED(HttpStatus.BAD_REQUEST, "이메일 인증이 필요합니다."), //Board BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."), From 789e460d78e08dc477f2d5f45065d18a15d373eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 00:57:09 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20swagger=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/java/team9502/sinchulgwinong/domain/auth/controller/AuthController.java b/src/main/java/team9502/sinchulgwinong/domain/auth/controller/AuthController.java index e4227ac..87b7f92 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/auth/controller/AuthController.java +++ b/src/main/java/team9502/sinchulgwinong/domain/auth/controller/AuthController.java @@ -27,7 +27,7 @@ @RestController @RequestMapping("/auth") @RequiredArgsConstructor -@Tag(name = "Auth", description = "인증 관련 API") +@Tag(name = "Auth", description = "인증 관련 API [김은채]") public class AuthController { private final AuthService authService; @@ -35,12 +35,18 @@ public class AuthController { @PostMapping("/signup") @Operation(summary = "구직자 회원 가입", description = "새로운 사용자를 등록합니다. 구직자 회원가입입니다.") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "회원 가입 성공", + @ApiResponse(responseCode = "201", description = "구직자 회원 가입 성공", content = @Content(mediaType = "application/json", - examples = @ExampleObject(value = "{ \"code\": \"200\", \"message\": \"회원 가입 성공\", \"data\": null }"))), - @ApiResponse(responseCode = "400", description = "회원 가입 실패", + examples = @ExampleObject(value = "{ \"code\": \"201\", \"message\": \"구직자 회원 가입 성공\", \"data\": null }"))), + @ApiResponse(responseCode = "400", description = "유효성 검사 실패", content = @Content(mediaType = "application/json", - examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"회원 가입 실패\", \"data\": null }"))), + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"유효성 검사 실패\", \"data\": null }"))), + @ApiResponse(responseCode = "400", description = "이메일 인증 필요", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"이메일 인증 필요\", \"data\": null }"))), + @ApiResponse(responseCode = "409", description = "중복된 이메일", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"409\", \"message\": \"중복된 이메일\", \"data\": null }"))), @ApiResponse(responseCode = "500", description = "서버 에러", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"500\", \"message\": \"서버 에러\", \"data\": null }"))) @@ -60,14 +66,20 @@ public ResponseEntity> signup( } @PostMapping("/cp-signup") - @Operation(summary = "기업 회원 가입", description = "새로운 기업 사용자를 등록합니다. 기업/구직자 회원가입입니다.") + @Operation(summary = "기업 회원 가입", description = "새로운 기업 사용자를 등록합니다. 기업(회원) 회원가입입니다.") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "회원 가입 성공", + @ApiResponse(responseCode = "201", description = "기업 회원 가입 성공", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"201\", \"message\": \"기업 회원 가입 성공\", \"data\": null }"))), + @ApiResponse(responseCode = "400", description = "유효성 검사 실패", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"유효성 검사 실패\", \"data\": null }"))), + @ApiResponse(responseCode = "400", description = "이메일 인증 필요", content = @Content(mediaType = "application/json", - examples = @ExampleObject(value = "{ \"code\": \"200\", \"message\": \"회원 가입 성공\", \"data\": null }"))), - @ApiResponse(responseCode = "400", description = "회원 가입 실패", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"이메일 인증 필요\", \"data\": null }"))), + @ApiResponse(responseCode = "409", description = "중복된 이메일", content = @Content(mediaType = "application/json", - examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"회원 가입 실패\", \"data\": null }"))), + examples = @ExampleObject(value = "{ \"code\": \"409\", \"message\": \"중복된 이메일\", \"data\": null }"))), @ApiResponse(responseCode = "500", description = "서버 에러", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"500\", \"message\": \"서버 에러\", \"data\": null }"))) From aa42e5e63ede5956cbf127ccf6a1845790567813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 01:03:22 +0900 Subject: [PATCH 08/10] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/companyUser/service/CpUserService.java | 6 +++++- .../sinchulgwinong/domain/user/service/UserService.java | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/team9502/sinchulgwinong/domain/companyUser/service/CpUserService.java b/src/main/java/team9502/sinchulgwinong/domain/companyUser/service/CpUserService.java index b145713..c21ab28 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/companyUser/service/CpUserService.java +++ b/src/main/java/team9502/sinchulgwinong/domain/companyUser/service/CpUserService.java @@ -1,6 +1,5 @@ package team9502.sinchulgwinong.domain.companyUser.service; - import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -10,6 +9,7 @@ import team9502.sinchulgwinong.domain.companyUser.dto.response.CpUserProfileResponseDTO; import team9502.sinchulgwinong.domain.companyUser.entity.CompanyUser; import team9502.sinchulgwinong.domain.companyUser.repository.CompanyUserRepository; +import team9502.sinchulgwinong.domain.email.service.EmailVerificationService; import team9502.sinchulgwinong.global.exception.ApiException; import team9502.sinchulgwinong.global.exception.ErrorCode; @@ -20,6 +20,7 @@ public class CpUserService { private final CompanyUserRepository companyUserRepository; private final EncryptionService encryptionService; private final PasswordEncoder passwordEncoder; + private final EmailVerificationService emailVerificationService; @Transactional(readOnly = true) public CpUserProfileResponseDTO getCpUserProfile(Long cpUserId) { @@ -59,6 +60,9 @@ public CpUserProfileResponseDTO updateCpUserProfile(Long cpUserId, CpUserProfile companyUser.setDescription(updateRequestDTO.getDescription()); } if (updateRequestDTO.getCpEmail() != null) { + if (!emailVerificationService.isEmailVerified(updateRequestDTO.getCpEmail())) { + throw new ApiException(ErrorCode.EMAIL_NOT_VERIFIED); + } companyUser.setCpEmail(updateRequestDTO.getCpEmail()); } if (updateRequestDTO.getCpPhoneNumber() != null) { diff --git a/src/main/java/team9502/sinchulgwinong/domain/user/service/UserService.java b/src/main/java/team9502/sinchulgwinong/domain/user/service/UserService.java index eaf345b..1546d8e 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/user/service/UserService.java +++ b/src/main/java/team9502/sinchulgwinong/domain/user/service/UserService.java @@ -4,6 +4,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import team9502.sinchulgwinong.domain.email.service.EmailVerificationService; import team9502.sinchulgwinong.domain.user.dto.request.UserPasswordUpdateRequestDTO; import team9502.sinchulgwinong.domain.user.dto.request.UserProfileUpdateRequestDTO; import team9502.sinchulgwinong.domain.user.dto.response.UserProfileResponseDTO; @@ -20,6 +21,7 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final EmailVerificationService emailVerificationService; @Transactional(readOnly = true) public UserProfileResponseDTO getUserProfile(Long userId) { @@ -48,6 +50,9 @@ public UserProfileResponseDTO updateUserProfile(Long userId, UserProfileUpdateRe user.setNickname(requestDTO.getNickname()); } if (requestDTO.getEmail() != null) { + if (!emailVerificationService.isEmailVerified(requestDTO.getEmail())) { + throw new ApiException(ErrorCode.EMAIL_NOT_VERIFIED); + } user.setEmail(requestDTO.getEmail()); } if (requestDTO.getPhoneNumber() != null) { From 8d1d379f012b8d1fecb77500a4672672f965566c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 01:05:48 +0900 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20swagger=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/companyUser/controller/CpUserController.java | 3 +++ .../domain/user/controller/UserController.java | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/team9502/sinchulgwinong/domain/companyUser/controller/CpUserController.java b/src/main/java/team9502/sinchulgwinong/domain/companyUser/controller/CpUserController.java index 5c5d814..1d88b93 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/companyUser/controller/CpUserController.java +++ b/src/main/java/team9502/sinchulgwinong/domain/companyUser/controller/CpUserController.java @@ -66,6 +66,9 @@ public ResponseEntity> getCompanyUse @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"요청 데이터가 유효하지 않습니다.\", \"data\": null }"))), + @ApiResponse(responseCode = "400", description = "이메일 인증 필요", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"이메일 인증이 필요합니다.\", \"data\": null }"))), @ApiResponse(responseCode = "404", description = "기업(회원)을 찾을 수 없음", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"404\", \"message\": \"기업(회원)을 찾을 수 없습니다.\", \"data\": null }"))), diff --git a/src/main/java/team9502/sinchulgwinong/domain/user/controller/UserController.java b/src/main/java/team9502/sinchulgwinong/domain/user/controller/UserController.java index 209b3a9..540d31c 100644 --- a/src/main/java/team9502/sinchulgwinong/domain/user/controller/UserController.java +++ b/src/main/java/team9502/sinchulgwinong/domain/user/controller/UserController.java @@ -63,9 +63,12 @@ public ResponseEntity> getUserProfile( @ApiResponse(responseCode = "200", description = "프로필 수정 성공", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"200\", \"message\": \"프로필 수정 성공\", \"data\": {\"userId\": 1, \"username\": \"김수정\", \"nickname\": \"수정이의 별명\", \"email\": \"fix@email.com\", \"phoneNumber\": \"010-5678-1234\"} }"))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", + @ApiResponse(responseCode = "400", description = "유효하지 않은 요청 데이터", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"요청 데이터가 유효하지 않습니다.\", \"data\": null }"))), + @ApiResponse(responseCode = "400", description = "이메일 인증 필요", + content = @Content(mediaType = "application/json", + examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"이메일 인증이 필요합니다.\", \"data\": null }"))), @ApiResponse(responseCode = "400", description = "잘못된 사용자 유형", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{ \"code\": \"400\", \"message\": \"잘못된 사용자 유형입니다.\", \"data\": null }"))), From bb85ccb8b5c944deaefa003c9fdef6fcd3e57672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9D=80=EC=B1=84?= Date: Mon, 17 Jun 2024 01:28:42 +0900 Subject: [PATCH 10/10] =?UTF-8?q?add:=20workflow=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sinChul.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/sinChul.yml b/.github/workflows/sinChul.yml index 1995918..b16c961 100644 --- a/.github/workflows/sinChul.yml +++ b/.github/workflows/sinChul.yml @@ -51,6 +51,8 @@ jobs: export S3_NAME="${{ secrets.S3_NAME }}" export S3_REGION="${{ secrets.S3_REGION }}" export SECRET_ACCESS_KEY="${{ secrets.SECRET_ACCESS_KEY }}" + export GOOGLE_ACCOUNT="${{ secrets.GOOGLE_ACCOUNT }}" + export GOOGLE_PASSWORD="${{ secrets.GOOGLE_PASSWORD }}" # JAR 파일을 /home/ec2-user 디렉토리에서 실행 nohup java -jar /home/ec2-user/*.jar > nohup.out 2>&1 & @@ -65,3 +67,5 @@ jobs: S3_NAME: ${{ secrets.S3_NAME }} S3_REGION: ${{ secrets.S3_REGION }} SECRET_ACCESS_KEY: ${{ secrets.SECRET_ACCESS_KEY }} + GOOGLE_ACCOUNT: ${{ secrets.GOOGLE_ACCOUNT }} + GOOGLE_PASSWORD: ${{ secrets.GOOGLE_PASSWORD }}