diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 046ccd8..ebeb7b3 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -142,3 +142,22 @@ include::{snippetsDir}/emailAuthentication/1/response-fields.adoc[] include::{snippetsDir}/emailAuthentication/2/http-response.adoc[] 실패 2 include::{snippetsDir}/emailAuthentication/3/http-response.adoc[] + + +=== **3. 이메일 인증 코드 검증 api** + +이메일 인증용 코드를 검증하는 api입니다. + +==== Request +include::{snippetsDir}/emailCodeVerification/1/http-request.adoc[] + +==== Request Body Fields +include::{snippetsDir}/emailCodeVerification/1/request-fields.adoc[] + +==== 성공 Response +include::{snippetsDir}/emailCodeVerification/1/http-response.adoc[] + +==== Response Body Fields +include::{snippetsDir}/emailCodeVerification/1/response-fields.adoc[] + + diff --git a/src/main/java/com/ftm/server/adapter/controller/user/EmailCodeVerificationController.java b/src/main/java/com/ftm/server/adapter/controller/user/EmailCodeVerificationController.java new file mode 100644 index 0000000..92e16c5 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/controller/user/EmailCodeVerificationController.java @@ -0,0 +1,35 @@ +package com.ftm.server.adapter.controller.user; + +import com.ftm.server.adapter.dto.request.EmailCodeVerificationRequest; +import com.ftm.server.adapter.dto.response.EmailCodeVerificationResponse; +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import com.ftm.server.domain.dto.query.EmailCodeVerificationQuery; +import com.ftm.server.domain.usecase.user.EmailCodeVerificationUseCase; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +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.RestController; + +@RestController +@RequiredArgsConstructor +public class EmailCodeVerificationController { + + private final EmailCodeVerificationUseCase emailCodeVerificationUseCase; + + @PostMapping("/api/users/email/authentication/code") + public ResponseEntity> controller( + @Valid @RequestBody EmailCodeVerificationRequest request) { + + EmailCodeVerificationResponse response = + EmailCodeVerificationResponse.from( + emailCodeVerificationUseCase.execute( + EmailCodeVerificationQuery.from(request))); + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(SuccessResponseCode.OK, response)); + } +} diff --git a/src/main/java/com/ftm/server/adapter/dto/request/EmailCodeVerificationRequest.java b/src/main/java/com/ftm/server/adapter/dto/request/EmailCodeVerificationRequest.java new file mode 100644 index 0000000..51e53b4 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/dto/request/EmailCodeVerificationRequest.java @@ -0,0 +1,12 @@ +package com.ftm.server.adapter.dto.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class EmailCodeVerificationRequest { + + @NotBlank private final String email; + + @NotBlank private final String code; +} diff --git a/src/main/java/com/ftm/server/adapter/dto/response/EmailCodeVerificationResponse.java b/src/main/java/com/ftm/server/adapter/dto/response/EmailCodeVerificationResponse.java new file mode 100644 index 0000000..5ca6ed8 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/dto/response/EmailCodeVerificationResponse.java @@ -0,0 +1,13 @@ +package com.ftm.server.adapter.dto.response; + +import com.ftm.server.domain.dto.vo.EmailCodeVerificationVo; +import lombok.Data; + +@Data +public class EmailCodeVerificationResponse { + private final Boolean isVerified; + + public static EmailCodeVerificationResponse from(EmailCodeVerificationVo vo) { + return new EmailCodeVerificationResponse(vo.getIsVerified()); + } +} diff --git a/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java b/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java index d4e6df5..b8a1628 100644 --- a/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java +++ b/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java @@ -10,4 +10,7 @@ public interface EmailVerificationLogsRepository extends JpaRepository { Optional findByEmail(String email); + + Optional findByVerificationCodeAndEmail( + String verificationCode, String email); } diff --git a/src/main/java/com/ftm/server/domain/dto/query/EmailCodeVerificationQuery.java b/src/main/java/com/ftm/server/domain/dto/query/EmailCodeVerificationQuery.java new file mode 100644 index 0000000..93e9aae --- /dev/null +++ b/src/main/java/com/ftm/server/domain/dto/query/EmailCodeVerificationQuery.java @@ -0,0 +1,14 @@ +package com.ftm.server.domain.dto.query; + +import com.ftm.server.adapter.dto.request.EmailCodeVerificationRequest; +import lombok.Data; + +@Data +public class EmailCodeVerificationQuery { + private final String code; + private final String email; + + public static EmailCodeVerificationQuery from(EmailCodeVerificationRequest request) { + return new EmailCodeVerificationQuery(request.getCode(), request.getEmail()); + } +} diff --git a/src/main/java/com/ftm/server/domain/dto/vo/EmailCodeVerificationVo.java b/src/main/java/com/ftm/server/domain/dto/vo/EmailCodeVerificationVo.java new file mode 100644 index 0000000..9fcc2cd --- /dev/null +++ b/src/main/java/com/ftm/server/domain/dto/vo/EmailCodeVerificationVo.java @@ -0,0 +1,12 @@ +package com.ftm.server.domain.dto.vo; + +import lombok.Data; + +@Data +public class EmailCodeVerificationVo { + private final Boolean isVerified; + + public static EmailCodeVerificationVo of(Boolean isVerified) { + return new EmailCodeVerificationVo(isVerified); + } +} diff --git a/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java b/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java index b54b2a6..4df816e 100644 --- a/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java +++ b/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java @@ -2,6 +2,7 @@ import com.ftm.server.adapter.gateway.repository.EmailVerificationLogsRepository; import com.ftm.server.domain.dto.command.EmailVerificationLogCreationCommand; +import com.ftm.server.domain.dto.query.EmailCodeVerificationQuery; import com.ftm.server.domain.dto.query.FindByEmailQuery; import com.ftm.server.entity.entities.EmailVerificationLogs; import java.util.Optional; @@ -21,4 +22,10 @@ public Optional findEmailVerificationLogsByEmail( public void saveEmailVerificationLogs(EmailVerificationLogCreationCommand command) { emailVerificationLogsRepository.save(EmailVerificationLogs.from(command)); } + + public Optional findByAuthenticationCode( + EmailCodeVerificationQuery query) { + return emailVerificationLogsRepository.findByVerificationCodeAndEmail( + query.getCode(), query.getEmail()); + } } diff --git a/src/main/java/com/ftm/server/domain/usecase/user/EmailCodeVerificationUseCase.java b/src/main/java/com/ftm/server/domain/usecase/user/EmailCodeVerificationUseCase.java new file mode 100644 index 0000000..64b8148 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/usecase/user/EmailCodeVerificationUseCase.java @@ -0,0 +1,30 @@ +package com.ftm.server.domain.usecase.user; + +import com.ftm.server.common.annotation.UseCase; +import com.ftm.server.domain.dto.query.EmailCodeVerificationQuery; +import com.ftm.server.domain.dto.vo.EmailCodeVerificationVo; +import com.ftm.server.domain.service.EmailVerificationLogsService; +import com.ftm.server.entity.entities.EmailVerificationLogs; +import jakarta.transaction.Transactional; +import java.util.Optional; +import lombok.RequiredArgsConstructor; + +@UseCase +@RequiredArgsConstructor +public class EmailCodeVerificationUseCase { + + private final EmailVerificationLogsService emailVerificationLogsService; + + @Transactional + public EmailCodeVerificationVo execute(EmailCodeVerificationQuery query) { + + Optional emailVerificationLogs = + emailVerificationLogsService.findByAuthenticationCode(query); + + if (emailVerificationLogs.isEmpty()) { // 검증 코드가 일치하지 않음 + return EmailCodeVerificationVo.of(false); + } + emailVerificationLogs.get().updateVerificationStatus(true); // 검증 코드가 일치함 + return EmailCodeVerificationVo.of(true); + } +} diff --git a/src/main/java/com/ftm/server/entity/entities/EmailVerificationLogs.java b/src/main/java/com/ftm/server/entity/entities/EmailVerificationLogs.java index 6a02d19..2b6bf3f 100644 --- a/src/main/java/com/ftm/server/entity/entities/EmailVerificationLogs.java +++ b/src/main/java/com/ftm/server/entity/entities/EmailVerificationLogs.java @@ -67,4 +67,8 @@ public void initializeVerificationStatus(String verificationCode) { this.verificationCode = verificationCode; this.tokenIssuanceTime = LocalDateTime.now(); } + + public void updateVerificationStatus(Boolean isVerified) { + this.isVerified = isVerified; + } } diff --git a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java index 50233ab..49f861c 100644 --- a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java +++ b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java @@ -43,7 +43,9 @@ public class SecurityConfig { private static final String[] GET_ANONYMOUS_MATCHERS = {"/api/users/email/duplication"}; - private static final String[] POST_ANONYMOUS_MATCHERS = {"/api/users/email/authentication"}; + private static final String[] POST_ANONYMOUS_MATCHERS = { + "/api/users/email/authentication", "/api/users/email/authentication/code" + }; private static final String[] ANONYMOUS_MATCHERS = {"/docs/**"}; diff --git a/src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java b/src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java new file mode 100644 index 0000000..df84779 --- /dev/null +++ b/src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java @@ -0,0 +1,90 @@ +package com.ftm.server.user; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import com.ftm.server.adapter.dto.request.EmailCodeVerificationRequest; +import com.ftm.server.domain.dto.command.EmailVerificationLogCreationCommand; +import com.ftm.server.domain.service.EmailVerificationLogsService; +import jakarta.transaction.Transactional; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +public class EmailCodeVerificationTest extends BaseTest { + + @Autowired private EmailVerificationLogsService emailVerificationLogsService; + + private final List requestFieldDescriptors = + List.of( + fieldWithPath("email").type(JsonFieldType.STRING).description("인증 email"), + fieldWithPath("code").type(JsonFieldType.STRING).description("인증 코드")); + + private final List responseFieldDescriptors = + List.of( + fieldWithPath("status").type(JsonFieldType.NUMBER).description("응답 상태"), + fieldWithPath("code").type(JsonFieldType.STRING).description("상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지"), + fieldWithPath("data").type(JsonFieldType.OBJECT).optional().description("data"), + fieldWithPath("data.isVerified") + .type(JsonFieldType.BOOLEAN) + .description("검증 성공 여부")); + + private ResultActions getResultActions(EmailCodeVerificationRequest request) throws Exception { + return mockMvc.perform( // api 실행 + RestDocumentationRequestBuilders.post("/api/users/email/authentication/code") + .contentType(MediaType.APPLICATION_JSON) // request body content type + .content(mapper.writeValueAsString(request))); + } + + // 문서화 반환 함수 + private RestDocumentationResultHandler getDocument(Integer identifier) { + return document( + "emailCodeVerification/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + responseFields(responseFieldDescriptors), + requestFields(requestFieldDescriptors), + resource( + ResourceSnippetParameters.builder() + .tag("회원") + .summary("이메일 인증 코드 검증 api") + .description("이메일 인증 코드를 검증하는 api입니다.") + .responseFields(responseFieldDescriptors) + .requestFields(requestFieldDescriptors) + .build())); + } + + @Test + @Transactional + void 이메일_인증코드_검증_성공() throws Exception { + + String email = "test@gmail.com"; + String code = "123456"; + // given + emailVerificationLogsService.saveEmailVerificationLogs( + EmailVerificationLogCreationCommand.of(email, code)); + + // when + ResultActions resultActions = + getResultActions(new EmailCodeVerificationRequest(email, code)); + + // then + resultActions.andExpect(status().isOk()); + + // documentation + resultActions.andDo(getDocument(1)); + } +}