diff --git a/.gitignore b/.gitignore index 288da7d..b072cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ out/ ### Propety ### *.properties +!gradle/wrapper/*.properties ### Key ### *.pem diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9..7f93135 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fa8f86 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index f5feea6..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -15,8 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# SPDX-License-Identifier: Apache-2.0 -# ############################################################################## # @@ -57,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -86,8 +84,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,6 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -45,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail @@ -59,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail diff --git a/src/main/java/sopt/makers/authentication/application/auth/api/AuthApiController.java b/src/main/java/sopt/makers/authentication/application/auth/api/AuthApiController.java index 1c6b92f..96a629b 100644 --- a/src/main/java/sopt/makers/authentication/application/auth/api/AuthApiController.java +++ b/src/main/java/sopt/makers/authentication/application/auth/api/AuthApiController.java @@ -10,10 +10,12 @@ import sopt.makers.authentication.usecase.auth.port.in.AuthenticateSocialAccountUsecase; import sopt.makers.authentication.usecase.auth.port.in.AuthenticateSocialAccountUsecase.AuthenticateTokenInfo; import sopt.makers.authentication.usecase.auth.port.in.CreatePhoneVerificationUsecase; +import sopt.makers.authentication.usecase.auth.port.in.VerifyPhoneVerificationUsecase; import org.springframework.http.HttpHeaders; 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; @@ -25,23 +27,29 @@ public class AuthApiController implements AuthApi { private final CreatePhoneVerificationUsecase createVerificationUsecase; + private final VerifyPhoneVerificationUsecase verifyVerificationUsecase; private final AuthenticateSocialAccountUsecase authenticateSocialAccountUsecase; private final CookieUtil cookieUtil; @Override @PostMapping("/phone") public ResponseEntity> createPhoneVerification( - AuthRequest.CreatePhoneVerification createPhoneVerificationRequest) { + @RequestBody AuthRequest.CreatePhoneVerification createPhoneVerificationRequest) { createVerificationUsecase.create(createPhoneVerificationRequest.toCommand()); return ResponseEntity.status(AuthSuccess.CREATE_PHONE_VERIFICATION.getStatus().value()) .body(BaseResponse.ofSuccess(AuthSuccess.CREATE_PHONE_VERIFICATION)); } @Override - @PostMapping("/verify/phone") + @PostMapping(value = "/verify/phone") public ResponseEntity> verifyPhoneVerification( - AuthRequest.VerifyPhoneVerification phoneVerification) { - return null; + @RequestBody AuthRequest.VerifyPhoneVerification phoneVerification) { + VerifyPhoneVerificationUsecase.VerifyVerificationResult result = + verifyVerificationUsecase.verify(phoneVerification.toCommand()); + return ResponseEntity.status(AuthSuccess.VERIFY_PHONE_VERIFICATION.getStatus().value()) + .body( + BaseResponse.ofSuccess( + AuthSuccess.VERIFY_PHONE_VERIFICATION, AuthResponse.VerifyResult.from(result))); } @Override diff --git a/src/main/java/sopt/makers/authentication/application/auth/dto/request/AuthRequest.java b/src/main/java/sopt/makers/authentication/application/auth/dto/request/AuthRequest.java index 0978a33..8b3177e 100644 --- a/src/main/java/sopt/makers/authentication/application/auth/dto/request/AuthRequest.java +++ b/src/main/java/sopt/makers/authentication/application/auth/dto/request/AuthRequest.java @@ -6,20 +6,36 @@ import sopt.makers.authentication.domain.auth.PhoneVerificationType; import sopt.makers.authentication.usecase.auth.port.in.AuthenticateSocialAccountUsecase.AuthenticateSocialAccountCommand; import sopt.makers.authentication.usecase.auth.port.in.CreatePhoneVerificationUsecase.CreateVerificationCommand; +import sopt.makers.authentication.usecase.auth.port.in.VerifyPhoneVerificationUsecase.VerifyVerificationCommand; + +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor(access = PRIVATE) public final class AuthRequest { - public record CreatePhoneVerification(String name, String number, String verificationTypeName) { + public record CreatePhoneVerification( + @JsonProperty("name") String name, + @JsonProperty("phone") String number, + @JsonProperty("type") String verificationTypeName) { public CreateVerificationCommand toCommand() { return new CreateVerificationCommand( this.name, this.number, PhoneVerificationType.valueOf(this.verificationTypeName)); } } - public record VerifyPhoneVerification(String name, String number, String code) {} + public record VerifyPhoneVerification( + @JsonProperty("name") String name, + @JsonProperty("phone") String number, + @JsonProperty("code") String code, + @JsonProperty("type") String verificationTypeName) { + public VerifyVerificationCommand toCommand() { + return new VerifyVerificationCommand( + this.name, + this.number, + this.code, + PhoneVerificationType.valueOf(this.verificationTypeName)); public record AuthenticateSocialAuthInfo(String code, String authPlatform) { public AuthenticateSocialAccountCommand toCommand() { diff --git a/src/main/java/sopt/makers/authentication/application/auth/dto/response/AuthResponse.java b/src/main/java/sopt/makers/authentication/application/auth/dto/response/AuthResponse.java index b3a08c4..d149ce7 100644 --- a/src/main/java/sopt/makers/authentication/application/auth/dto/response/AuthResponse.java +++ b/src/main/java/sopt/makers/authentication/application/auth/dto/response/AuthResponse.java @@ -2,10 +2,20 @@ import static lombok.AccessLevel.PRIVATE; +import sopt.makers.authentication.usecase.auth.port.in.VerifyPhoneVerificationUsecase; + +import com.fasterxml.jackson.annotation.JsonProperty; + import lombok.RequiredArgsConstructor; @RequiredArgsConstructor(access = PRIVATE) public final class AuthResponse { + + public record VerifyResult(@JsonProperty("isVerified") boolean isVerified) { + public static VerifyResult from( + VerifyPhoneVerificationUsecase.VerifyVerificationResult result) { + return new VerifyResult(result.isVerifySuccess()); + public record AuthenticateSocialAuthInfoForWeb(String accessToken) { public static AuthenticateSocialAuthInfoForWeb of(String accessToken) { return new AuthenticateSocialAuthInfoForWeb(accessToken); diff --git a/src/main/java/sopt/makers/authentication/database/PhoneVerificationRepositoryImpl.java b/src/main/java/sopt/makers/authentication/database/PhoneVerificationRepositoryImpl.java new file mode 100644 index 0000000..19ec9c8 --- /dev/null +++ b/src/main/java/sopt/makers/authentication/database/PhoneVerificationRepositoryImpl.java @@ -0,0 +1,38 @@ +package sopt.makers.authentication.database; + +import sopt.makers.authentication.database.rdb.entity.auth.PhoneVerificationEntity; +import sopt.makers.authentication.database.rdb.repository.auth.PhoneVerificationRegister; +import sopt.makers.authentication.database.rdb.repository.auth.PhoneVerificationRemover; +import sopt.makers.authentication.database.rdb.repository.auth.PhoneVerificationRetriever; +import sopt.makers.authentication.domain.auth.PhoneVerification; +import sopt.makers.authentication.usecase.auth.port.out.PhoneVerificationRepository; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class PhoneVerificationRepositoryImpl implements PhoneVerificationRepository { + + private final PhoneVerificationRegister register; + private final PhoneVerificationRetriever retriever; + private final PhoneVerificationRemover remover; + + @Override + public PhoneVerification create(PhoneVerification phoneVerification) { + PhoneVerificationEntity createdEntity = register.register(phoneVerification); + return createdEntity.toDomain(); + } + + @Override + public PhoneVerification findByPhoneVerification(PhoneVerification phoneVerification) { + PhoneVerificationEntity phoneVerificationEntity = retriever.find(phoneVerification); + return phoneVerificationEntity.toDomain(); + } + + @Override + public void deletedByPhoneVerification(PhoneVerification phoneVerification) { + remover.remove(phoneVerification); + } +} diff --git a/src/main/java/sopt/makers/authentication/database/rdb/entity/PhoneVerificationEntity.java b/src/main/java/sopt/makers/authentication/database/rdb/entity/auth/PhoneVerificationEntity.java similarity index 94% rename from src/main/java/sopt/makers/authentication/database/rdb/entity/PhoneVerificationEntity.java rename to src/main/java/sopt/makers/authentication/database/rdb/entity/auth/PhoneVerificationEntity.java index fd5f0c5..47d1960 100644 --- a/src/main/java/sopt/makers/authentication/database/rdb/entity/PhoneVerificationEntity.java +++ b/src/main/java/sopt/makers/authentication/database/rdb/entity/auth/PhoneVerificationEntity.java @@ -1,4 +1,4 @@ -package sopt.makers.authentication.database.rdb.entity; +package sopt.makers.authentication.database.rdb.entity.auth; import static lombok.AccessLevel.PRIVATE; import static lombok.AccessLevel.PROTECTED; @@ -22,7 +22,7 @@ @AllArgsConstructor(access = PRIVATE) public class PhoneVerificationEntity extends BaseEntity { - @Column(name = "name", nullable = false) + @Column(name = "name") private String name; @Column(name = "phone", nullable = false) diff --git a/src/main/java/sopt/makers/authentication/database/rdb/repository/PhoneVerificationJpaRepository.java b/src/main/java/sopt/makers/authentication/database/rdb/repository/PhoneVerificationJpaRepository.java deleted file mode 100644 index 485c4c6..0000000 --- a/src/main/java/sopt/makers/authentication/database/rdb/repository/PhoneVerificationJpaRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package sopt.makers.authentication.database.rdb.repository; - -import sopt.makers.authentication.database.rdb.entity.PhoneVerificationEntity; - -import org.springframework.data.jpa.repository.JpaRepository; - -interface PhoneVerificationJpaRepository extends JpaRepository {} diff --git a/src/main/java/sopt/makers/authentication/database/rdb/repository/PhoneVerificationRepositoryImpl.java b/src/main/java/sopt/makers/authentication/database/rdb/repository/PhoneVerificationRepositoryImpl.java deleted file mode 100644 index 52c1cff..0000000 --- a/src/main/java/sopt/makers/authentication/database/rdb/repository/PhoneVerificationRepositoryImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -package sopt.makers.authentication.database.rdb.repository; - -import sopt.makers.authentication.database.rdb.entity.PhoneVerificationEntity; -import sopt.makers.authentication.domain.auth.PhoneVerification; -import sopt.makers.authentication.usecase.auth.port.out.PhoneVerificationRepository; - -import org.springframework.stereotype.Repository; - -import lombok.RequiredArgsConstructor; - -@Repository -@RequiredArgsConstructor -public class PhoneVerificationRepositoryImpl implements PhoneVerificationRepository { - - private final PhoneVerificationJpaRepository jpaRepository; - - @Override - public PhoneVerification save(PhoneVerification phoneVerification) { - PhoneVerificationEntity createdEntity = - jpaRepository.save(PhoneVerificationEntity.fromDomain(phoneVerification)); - return createdEntity.toDomain(); - } -} diff --git a/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationJpaRepository.java b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationJpaRepository.java new file mode 100644 index 0000000..f523b7f --- /dev/null +++ b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationJpaRepository.java @@ -0,0 +1,17 @@ +package sopt.makers.authentication.database.rdb.repository.auth; + +import sopt.makers.authentication.database.rdb.entity.auth.PhoneVerificationEntity; +import sopt.makers.authentication.domain.auth.PhoneVerificationType; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +interface PhoneVerificationJpaRepository extends JpaRepository { + + Optional findByPhoneAndCodeAndType( + String phone, String code, PhoneVerificationType type); + + void deleteByNameAndPhoneAndCodeAndType( + String name, String phone, String code, PhoneVerificationType type); +} diff --git a/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRegister.java b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRegister.java new file mode 100644 index 0000000..9799e1c --- /dev/null +++ b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRegister.java @@ -0,0 +1,28 @@ +package sopt.makers.authentication.database.rdb.repository.auth; + +import sopt.makers.authentication.database.rdb.entity.auth.PhoneVerificationEntity; +import sopt.makers.authentication.domain.auth.PhoneVerification; + +import jakarta.transaction.Transactional; + +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Component +@Transactional +@RequiredArgsConstructor +public class PhoneVerificationRegister { + + private final PhoneVerificationJpaRepository jpaRepository; + + public PhoneVerificationEntity register(PhoneVerification phoneVerification) { + PhoneVerificationEntity entity = PhoneVerificationEntity.fromDomain(phoneVerification); + return jpaRepository.save(entity); + } + + public PhoneVerificationEntity register(final long id, PhoneVerification phoneVerification) { + PhoneVerificationEntity entity = PhoneVerificationEntity.fromDomain(id, phoneVerification); + return jpaRepository.save(entity); + } +} diff --git a/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRemover.java b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRemover.java new file mode 100644 index 0000000..cb124d8 --- /dev/null +++ b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRemover.java @@ -0,0 +1,25 @@ +package sopt.makers.authentication.database.rdb.repository.auth; + +import sopt.makers.authentication.domain.auth.PhoneVerification; + +import jakarta.transaction.Transactional; + +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Component +@Transactional +@RequiredArgsConstructor +public class PhoneVerificationRemover { + + private final PhoneVerificationJpaRepository jpaRepository; + + public void remove(PhoneVerification phoneVerification) { + jpaRepository.deleteByNameAndPhoneAndCodeAndType( + phoneVerification.getName(), + phoneVerification.getPhone(), + phoneVerification.getVerificationCode().getCode(), + phoneVerification.getVerificationType()); + } +} diff --git a/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRetriever.java b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRetriever.java new file mode 100644 index 0000000..95a1f71 --- /dev/null +++ b/src/main/java/sopt/makers/authentication/database/rdb/repository/auth/PhoneVerificationRetriever.java @@ -0,0 +1,28 @@ +package sopt.makers.authentication.database.rdb.repository.auth; + +import sopt.makers.authentication.database.rdb.entity.auth.PhoneVerificationEntity; +import sopt.makers.authentication.domain.auth.PhoneVerification; +import sopt.makers.authentication.support.code.domain.failure.AuthFailure; +import sopt.makers.authentication.support.exception.domain.AuthException; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Component +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class PhoneVerificationRetriever { + + private final PhoneVerificationJpaRepository jpaRepository; + + public PhoneVerificationEntity find(PhoneVerification phoneVerification) { + return jpaRepository + .findByPhoneAndCodeAndType( + phoneVerification.getPhone(), + phoneVerification.getVerificationCode().getCode(), + phoneVerification.getVerificationType()) + .orElseThrow(() -> new AuthException(AuthFailure.NOT_FOUND_PHONE_VERIFICATION)); + } +} diff --git a/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerification.java b/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerification.java index 3c84b38..0c1d32e 100644 --- a/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerification.java +++ b/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerification.java @@ -5,12 +5,14 @@ import java.util.Random; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @Builder(access = PRIVATE) @RequiredArgsConstructor(access = PRIVATE) +@EqualsAndHashCode public class PhoneVerification { private final String name; @@ -40,6 +42,7 @@ public static PhoneVerification create(String name, String phone, PhoneVerificat } @Getter + @EqualsAndHashCode public static class VerificationCode { private static final int CODE_SIZE = 6; private final String code; diff --git a/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerificationType.java b/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerificationType.java index 6c18930..f185ef9 100644 --- a/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerificationType.java +++ b/src/main/java/sopt/makers/authentication/domain/auth/PhoneVerificationType.java @@ -3,5 +3,6 @@ public enum PhoneVerificationType { REGISTER, SEARCH, - CHANGE + CHANGE, + ; } diff --git a/src/main/java/sopt/makers/authentication/support/code/domain/failure/AuthFailure.java b/src/main/java/sopt/makers/authentication/support/code/domain/failure/AuthFailure.java index 1b0f392..5bce0f6 100644 --- a/src/main/java/sopt/makers/authentication/support/code/domain/failure/AuthFailure.java +++ b/src/main/java/sopt/makers/authentication/support/code/domain/failure/AuthFailure.java @@ -12,8 +12,14 @@ @Getter @RequiredArgsConstructor(access = PRIVATE) public enum AuthFailure implements FailureCode { + // 400 INVALID_SOCIAL_PLATFORM(HttpStatus.BAD_REQUEST, "지원하지 않는 소셜 플랫폼입니다"), - NOT_FOUND_USER_WITH_SOCIAL_ACCOUNT(HttpStatus.BAD_REQUEST, "소셜 계정 정보와 일치하는 회원이 없습니다"); + + // 404 + NOT_FOUND_PHONE_VERIFICATION(HttpStatus.NOT_FOUND, "존재하지 않는 번호 인증 이력입니다."), + NOT_FOUND_USER_WITH_SOCIAL_ACCOUNT(HttpStatus.BAD_REQUEST, "소셜 계정 정보와 일치하는 회원이 없습니다"), + ; + private final HttpStatus status; private final String message; } diff --git a/src/main/java/sopt/makers/authentication/support/code/domain/success/AuthSuccess.java b/src/main/java/sopt/makers/authentication/support/code/domain/success/AuthSuccess.java index dd47753..ed90b05 100644 --- a/src/main/java/sopt/makers/authentication/support/code/domain/success/AuthSuccess.java +++ b/src/main/java/sopt/makers/authentication/support/code/domain/success/AuthSuccess.java @@ -12,6 +12,8 @@ @Getter @RequiredArgsConstructor(access = PRIVATE) public enum AuthSuccess implements SuccessCode { + VERIFY_PHONE_VERIFICATION(HttpStatus.OK, "번호 인증에 성공했습니다."), + CREATE_PHONE_VERIFICATION(HttpStatus.CREATED, "번호 인증 생성에 성공했습니다."), AUTHENTICATE_SOCIAL_ACCOUNT(HttpStatus.OK, "소셜 로그인에 성공했습니다."); diff --git a/src/main/java/sopt/makers/authentication/support/common/api/BaseResponse.java b/src/main/java/sopt/makers/authentication/support/common/api/BaseResponse.java index 32c9615..8e90795 100644 --- a/src/main/java/sopt/makers/authentication/support/common/api/BaseResponse.java +++ b/src/main/java/sopt/makers/authentication/support/common/api/BaseResponse.java @@ -5,15 +5,24 @@ import sopt.makers.authentication.support.code.base.FailureCode; import sopt.makers.authentication.support.code.base.SuccessCode; +import com.fasterxml.jackson.annotation.JsonProperty; + import lombok.Builder; +import lombok.Getter; import lombok.RequiredArgsConstructor; +@Getter @Builder(access = PRIVATE) @RequiredArgsConstructor(access = PRIVATE) public class BaseResponse { + @JsonProperty("success") private final boolean isSuccess; + + @JsonProperty("message") private final String message; + + @JsonProperty("data") private final T data; public static BaseResponse ofFailure(FailureCode failure, T data) { diff --git a/src/main/java/sopt/makers/authentication/usecase/auth/port/in/VerifyPhoneVerificationUsecase.java b/src/main/java/sopt/makers/authentication/usecase/auth/port/in/VerifyPhoneVerificationUsecase.java new file mode 100644 index 0000000..66efbaf --- /dev/null +++ b/src/main/java/sopt/makers/authentication/usecase/auth/port/in/VerifyPhoneVerificationUsecase.java @@ -0,0 +1,13 @@ +package sopt.makers.authentication.usecase.auth.port.in; + +import sopt.makers.authentication.domain.auth.PhoneVerificationType; + +public interface VerifyPhoneVerificationUsecase { + + VerifyVerificationResult verify(VerifyVerificationCommand command); + + record VerifyVerificationCommand( + String name, String phone, String code, PhoneVerificationType verificationType) {} + + record VerifyVerificationResult(boolean isVerifySuccess) {} +} diff --git a/src/main/java/sopt/makers/authentication/usecase/auth/port/out/PhoneVerificationRepository.java b/src/main/java/sopt/makers/authentication/usecase/auth/port/out/PhoneVerificationRepository.java index 98ba17f..cfe7457 100644 --- a/src/main/java/sopt/makers/authentication/usecase/auth/port/out/PhoneVerificationRepository.java +++ b/src/main/java/sopt/makers/authentication/usecase/auth/port/out/PhoneVerificationRepository.java @@ -4,5 +4,9 @@ public interface PhoneVerificationRepository { - PhoneVerification save(PhoneVerification phoneVerification); + PhoneVerification create(PhoneVerification phoneVerification); + + PhoneVerification findByPhoneVerification(PhoneVerification phoneVerification); + + void deletedByPhoneVerification(PhoneVerification phoneVerification); } diff --git a/src/main/java/sopt/makers/authentication/usecase/auth/service/CreateVerificationService.java b/src/main/java/sopt/makers/authentication/usecase/auth/service/CreateVerificationService.java index 313f075..d8b0a25 100644 --- a/src/main/java/sopt/makers/authentication/usecase/auth/service/CreateVerificationService.java +++ b/src/main/java/sopt/makers/authentication/usecase/auth/service/CreateVerificationService.java @@ -29,7 +29,7 @@ public PhoneVerification create(CreateVerificationCommand command) { convertCodeToMessage(phoneVerification.getVerificationCode().getCode())); messageSendPort.sendMessage(verificationMessage); - return verificationRepository.save(phoneVerification); + return verificationRepository.create(phoneVerification); } private String convertCodeToMessage(String code) { diff --git a/src/main/java/sopt/makers/authentication/usecase/auth/service/VerifyVerificationService.java b/src/main/java/sopt/makers/authentication/usecase/auth/service/VerifyVerificationService.java new file mode 100644 index 0000000..094d274 --- /dev/null +++ b/src/main/java/sopt/makers/authentication/usecase/auth/service/VerifyVerificationService.java @@ -0,0 +1,30 @@ +package sopt.makers.authentication.usecase.auth.service; + +import sopt.makers.authentication.domain.auth.PhoneVerification; +import sopt.makers.authentication.usecase.auth.port.in.VerifyPhoneVerificationUsecase; +import sopt.makers.authentication.usecase.auth.port.out.PhoneVerificationRepository; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class VerifyVerificationService implements VerifyPhoneVerificationUsecase { + private final PhoneVerificationRepository phoneVerificationRepository; + + @Override + public VerifyVerificationResult verify(VerifyVerificationCommand command) { + PhoneVerification targetVerification = + PhoneVerification.of( + command.name(), command.phone(), command.verificationType(), command.code()); + PhoneVerification findVerification = + phoneVerificationRepository.findByPhoneVerification(targetVerification); + boolean isVerified = targetVerification.equals(findVerification); + + if (isVerified) { + phoneVerificationRepository.deletedByPhoneVerification(findVerification); + } + return new VerifyVerificationResult(isVerified); + } +} diff --git a/src/test/java/sopt/makers/authentication/application/auth/api/AuthApiControllerTest.java b/src/test/java/sopt/makers/authentication/application/auth/api/AuthApiControllerTest.java new file mode 100644 index 0000000..d093ee7 --- /dev/null +++ b/src/test/java/sopt/makers/authentication/application/auth/api/AuthApiControllerTest.java @@ -0,0 +1,57 @@ +package sopt.makers.authentication.application.auth.api; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static sopt.makers.authentication.usecase.auth.port.in.VerifyPhoneVerificationUsecase.*; + +import sopt.makers.authentication.usecase.auth.port.in.CreatePhoneVerificationUsecase; +import sopt.makers.authentication.usecase.auth.port.in.VerifyPhoneVerificationUsecase; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +@ExtendWith(MockitoExtension.class) +@ContextConfiguration(classes = {AuthApiController.class}) +@WebMvcTest( + controllers = {AuthApiController.class}, + excludeAutoConfiguration = {SecurityAutoConfiguration.class}) +@ActiveProfiles("test") +class AuthApiControllerTest { + @Autowired MockMvc mockMvc; + @MockBean VerifyPhoneVerificationUsecase verifyPhoneVerificationUsecase; + @MockBean CreatePhoneVerificationUsecase createPhoneVerificationUsecase; + + @Test + void verifyPhoneVerification() throws Exception { + // given + String givenRequestBodyContent = + "{\"name\":\"TEST\",\"phone\":\"01012345678\",\"code\":\"123456\",\"type\":\"REGISTER\"}"; + + // when + when(verifyPhoneVerificationUsecase.verify(any(VerifyVerificationCommand.class))) + .thenReturn(new VerifyVerificationResult(true)); + mockMvc + .perform( + post("/api/v1/auth/verify/phone") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .characterEncoding("UTF-8") + .content(givenRequestBodyContent)) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value("true")) + .andExpect(jsonPath("$.message").value("번호 인증에 성공했습니다.")) + .andExpect(jsonPath("$.data.isVerified").value("true")); + } +} diff --git a/src/test/java/sopt/makers/authentication/database/PhoneVerificationRepositoryImplTest.java b/src/test/java/sopt/makers/authentication/database/PhoneVerificationRepositoryImplTest.java new file mode 100644 index 0000000..734f814 --- /dev/null +++ b/src/test/java/sopt/makers/authentication/database/PhoneVerificationRepositoryImplTest.java @@ -0,0 +1,52 @@ +package sopt.makers.authentication.database; + +import static org.assertj.core.api.Assertions.assertThat; + +import sopt.makers.authentication.domain.auth.PhoneVerification; +import sopt.makers.authentication.domain.auth.PhoneVerificationType; +import sopt.makers.authentication.usecase.auth.port.out.PhoneVerificationRepository; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("test") +class PhoneVerificationRepositoryImplTest { + + @Autowired private PhoneVerificationRepository phoneVerificationRepository; + + @BeforeEach + void initTestPhoneVerification() { + PhoneVerification testVerification = + PhoneVerification.of(null, "01012345678", PhoneVerificationType.REGISTER, "123456"); + phoneVerificationRepository.create(testVerification); + } + + @AfterEach + void flushTestPhoneVerification() { + PhoneVerification testVerification = + PhoneVerification.of(null, "01012345678", PhoneVerificationType.REGISTER, "123456"); + phoneVerificationRepository.deletedByPhoneVerification(testVerification); + } + + @Test + @DisplayName("Name이 Null이더라도 정상 조회가 가능하다.") + void findByVerificationNameNull() { + // given + PhoneVerification givenVerification = + PhoneVerification.of(null, "01012345678", PhoneVerificationType.REGISTER, "123456"); + PhoneVerification findVerification = + phoneVerificationRepository.findByPhoneVerification(givenVerification); + + // when + boolean result = givenVerification.equals(findVerification); + + // then + assertThat(result).isTrue(); + } +} diff --git a/src/test/java/sopt/makers/authentication/domain/auth/PhoneVerificationTest.java b/src/test/java/sopt/makers/authentication/domain/auth/PhoneVerificationTest.java new file mode 100644 index 0000000..dfd37da --- /dev/null +++ b/src/test/java/sopt/makers/authentication/domain/auth/PhoneVerificationTest.java @@ -0,0 +1,66 @@ +package sopt.makers.authentication.domain.auth; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PhoneVerificationTest { + + private PhoneVerification testPhoneVerification; + + @BeforeEach + void setTestPhoneVerification() { + this.testPhoneVerification = + PhoneVerification.of("test", "01012345678", PhoneVerificationType.REGISTER, "123456"); + } + + @Test + @DisplayName("동일한 필드(이름, 번호, 코드, 인증 타입)을 가질 경우, Equals 연산 시 참을 반환한다.") + void testEqualsTrue() { + // given + PhoneVerification givenPhoneVerification = + PhoneVerification.of( + "test", + "01012345678", + PhoneVerificationType.REGISTER, + testPhoneVerification.getVerificationCode().getCode()); + + // when + boolean result = testPhoneVerification.equals(givenPhoneVerification); + + // then + assertThat(result).isTrue(); + } + + @ParameterizedTest + @MethodSource("argsForNotEqualPhoneVerification") + @DisplayName("한 개라도 필드의 값이 다를 경우, Equals 연산 시 거짓을 반환한다.") + void testEqualsFalse( + // given + PhoneVerification givenPhoneVerification) { + // when + boolean result = testPhoneVerification.equals(givenPhoneVerification); + + // then + assertThat(result).isFalse(); + } + + static Stream argsForNotEqualPhoneVerification() { + return Stream.of( + Arguments.of( + PhoneVerification.of("testA", "01012345678", PhoneVerificationType.REGISTER, "123456")), + Arguments.of( + PhoneVerification.of("test", "01011345678", PhoneVerificationType.REGISTER, "123456")), + Arguments.of( + PhoneVerification.of("test", "01012345678", PhoneVerificationType.CHANGE, "123456")), + Arguments.of( + PhoneVerification.of("test", "01012345678", PhoneVerificationType.REGISTER, "123455"))); + } +} diff --git a/src/test/java/sopt/makers/authentication/usecase/auth/service/VerifyVerificationServiceTest.java b/src/test/java/sopt/makers/authentication/usecase/auth/service/VerifyVerificationServiceTest.java new file mode 100644 index 0000000..4a0166b --- /dev/null +++ b/src/test/java/sopt/makers/authentication/usecase/auth/service/VerifyVerificationServiceTest.java @@ -0,0 +1,53 @@ +package sopt.makers.authentication.usecase.auth.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static sopt.makers.authentication.usecase.auth.port.in.VerifyPhoneVerificationUsecase.*; + +import sopt.makers.authentication.domain.auth.PhoneVerification; +import sopt.makers.authentication.domain.auth.PhoneVerificationType; +import sopt.makers.authentication.support.code.domain.failure.AuthFailure; +import sopt.makers.authentication.support.exception.domain.AuthException; +import sopt.makers.authentication.usecase.auth.port.out.PhoneVerificationRepository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("test") +class VerifyVerificationServiceTest { + + @Autowired private PhoneVerificationRepository phoneVerificationRepository; + @Autowired private VerifyVerificationService verifyService; + + @BeforeEach + void setExistVerification() { + PhoneVerification testVerification = + PhoneVerification.of(null, "01012345678", PhoneVerificationType.REGISTER, "123456"); + phoneVerificationRepository.create(testVerification); + } + + @Test + void verifySuccessTest() { + // given + String givenVerifyPhone = "01012345678"; + PhoneVerificationType givenVerifyType = PhoneVerificationType.REGISTER; + String givenCode = "123456"; + PhoneVerification givenVerification = + PhoneVerification.of(null, givenVerifyPhone, givenVerifyType, givenCode); + VerifyVerificationCommand givenCommand = + new VerifyVerificationCommand(null, givenVerifyPhone, givenCode, givenVerifyType); + + // when + VerifyVerificationResult result = verifyService.verify(givenCommand); + + // then + assertThat(result.isVerifySuccess()).isTrue(); + assertThatThrownBy(() -> phoneVerificationRepository.findByPhoneVerification(givenVerification)) + .isInstanceOf(AuthException.class) + .hasMessageContaining(AuthFailure.NOT_FOUND_PHONE_VERIFICATION.getMessage()); + } +}