Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]


Original file line number Diff line number Diff line change
@@ -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<ApiResponse<EmailCodeVerificationResponse>> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ public interface EmailVerificationLogsRepository
extends JpaRepository<EmailVerificationLogs, Long> {

Optional<EmailVerificationLogs> findByEmail(String email);

Optional<EmailVerificationLogs> findByVerificationCodeAndEmail(
String verificationCode, String email);
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,4 +22,10 @@ public Optional<EmailVerificationLogs> findEmailVerificationLogsByEmail(
public void saveEmailVerificationLogs(EmailVerificationLogCreationCommand command) {
emailVerificationLogsRepository.save(EmailVerificationLogs.from(command));
}

public Optional<EmailVerificationLogs> findByAuthenticationCode(
EmailCodeVerificationQuery query) {
return emailVerificationLogsRepository.findByVerificationCodeAndEmail(
query.getCode(), query.getEmail());
}
}
Original file line number Diff line number Diff line change
@@ -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> emailVerificationLogs =
emailVerificationLogsService.findByAuthenticationCode(query);

if (emailVerificationLogs.isEmpty()) { // 검증 μ½”λ“œκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŒ
return EmailCodeVerificationVo.of(false);
}
emailVerificationLogs.get().updateVerificationStatus(true); // 검증 μ½”λ“œκ°€ μΌμΉ˜ν•¨
return EmailCodeVerificationVo.of(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,8 @@ public void initializeVerificationStatus(String verificationCode) {
this.verificationCode = verificationCode;
this.tokenIssuanceTime = LocalDateTime.now();
}

public void updateVerificationStatus(Boolean isVerified) {
this.isVerified = isVerified;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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/**"};

Expand Down
90 changes: 90 additions & 0 deletions src/test/java/com/ftm/server/user/EmailCodeVerificationTest.java
Original file line number Diff line number Diff line change
@@ -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<FieldDescriptor> requestFieldDescriptors =
List.of(
fieldWithPath("email").type(JsonFieldType.STRING).description("인증 email"),
fieldWithPath("code").type(JsonFieldType.STRING).description("인증 μ½”λ“œ"));

private final List<FieldDescriptor> 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));
}
}