From c8a1614b872a98cc4956cbd90fb1ae8a6dbdcc1a Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 21:44:26 +0900 Subject: [PATCH 001/157] =?UTF-8?q?fix=20:=20api=20docs=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파라미터 설명 수정 - 보물 캡슐 api 문서 그룹화 --- .../domain/capsule/group_capsule/api/GroupCapsuleApi.java | 2 +- .../capsule/treasure_capsule/api/TreasureCapsuleApi.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java index 484c97c2e..dae77ac03 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java @@ -180,7 +180,7 @@ ResponseEntity> getGroupCapsuleMembers( @Parameter(in = ParameterIn.PATH, description = "개봉 상태를 확인할 캡슐 아이디", required = true) Long capsuleId, - @Parameter(in = ParameterIn.QUERY, description = "생성할 그룹 아이디", required = true) + @Parameter(in = ParameterIn.QUERY, description = "캡슐이 속한 그룹 아이디", required = true) Long groupId ); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/api/TreasureCapsuleApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/api/TreasureCapsuleApi.java index fec73b869..a061290ee 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/api/TreasureCapsuleApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/api/TreasureCapsuleApi.java @@ -20,7 +20,7 @@ public interface TreasureCapsuleApi { summary = "보물 캡슐 개봉", description = "사용자는 보물 캡슐을 개봉할 수 있다.", security = {@SecurityRequirement(name = "user_token")}, - tags = {"secret capsule"} + tags = {"treasure capsule"} ) @ApiResponses(value = { @ApiResponse( @@ -49,7 +49,7 @@ ResponseEntity> openTreasureCapsule( summary = "보물 캡슐 요약 조회", description = "사용자는 보물 캡슐 정보를 요약 조회할 수 있다.", security = {@SecurityRequirement(name = "user_token")}, - tags = {"secret capsule"} + tags = {"treasure capsule"} ) @ApiResponses(value = { @ApiResponse( From b2753974b6d93bc63e9d672be9a73c40bd7f8b32 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:25:25 +0900 Subject: [PATCH 002/157] =?UTF-8?q?refact=20:=20auth=20manager=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20oauth2=20url=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/api/AuthApiController.java | 17 +++------- .../core/domain/auth/service/AuthManager.java | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index ab37c0ac9..8f885acad 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -22,6 +22,7 @@ import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.VerificationMessageSendResponse; +import site.timecapsulearchive.core.domain.auth.service.AuthManager; import site.timecapsulearchive.core.domain.auth.service.MessageVerificationService; import site.timecapsulearchive.core.domain.auth.service.TokenManager; import site.timecapsulearchive.core.domain.member.service.MemberService; @@ -33,9 +34,7 @@ @RequestMapping("/auth") public class AuthApiController implements AuthApi { - private static final String KAKAO_AUTHORIZATION_ENDPOINT = "/auth/login/kakao"; - private static final String GOOGLE_AUTHORIZATION_ENDPOINT = "/auth/login/google"; - + private final AuthManager authManager; private final TokenManager tokenService; private final MessageVerificationService messageVerificationService; private final MemberService memberService; @@ -44,11 +43,7 @@ public class AuthApiController implements AuthApi { @GetMapping(value = "/login/url/kakao", produces = {"application/json"}) @Override public ResponseEntity getOAuth2KakaoUrl(final HttpServletRequest request) { - final String baseUrl = request.getRequestURL().toString(); - final String kakaoLoginUrl = baseUrl.replace( - request.getRequestURI(), - request.getContextPath() + KAKAO_AUTHORIZATION_ENDPOINT - ); + String kakaoLoginUrl = authManager.getOAuth2KakaoUrl(request); return ResponseEntity.ok(OAuth2UriResponse.from(kakaoLoginUrl)); } @@ -56,11 +51,7 @@ public ResponseEntity getOAuth2KakaoUrl(final HttpServletRequ @GetMapping(value = "/login/url/google", produces = {"application/json"}) @Override public ResponseEntity getOAuth2GoogleUrl(final HttpServletRequest request) { - final String baseUrl = request.getRequestURL().toString(); - final String googleLoginUrl = baseUrl.replace( - request.getRequestURI(), - request.getContextPath() + GOOGLE_AUTHORIZATION_ENDPOINT - ); + String googleLoginUrl = authManager.getOauth2GoogleUrl(request); return ResponseEntity.ok(OAuth2UriResponse.from(googleLoginUrl)); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java new file mode 100644 index 000000000..573b0cfb8 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -0,0 +1,31 @@ +package site.timecapsulearchive.core.domain.auth.service; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AuthManager { + + private static final String KAKAO_AUTHORIZATION_ENDPOINT = "/auth/login/kakao"; + private static final String GOOGLE_AUTHORIZATION_ENDPOINT = "/auth/login/google"; + + public String getOAuth2KakaoUrl(final HttpServletRequest request) { + final String baseUrl = request.getRequestURL().toString(); + + return baseUrl.replace( + request.getRequestURI(), + request.getContextPath() + KAKAO_AUTHORIZATION_ENDPOINT + ); + } + + public String getOauth2GoogleUrl(HttpServletRequest request) { + final String baseUrl = request.getRequestURL().toString(); + + return baseUrl.replace( + request.getRequestURI(), + request.getContextPath() + GOOGLE_AUTHORIZATION_ENDPOINT + ); + } +} From 9f8c055fc977f62d76d87b57441f20af45f0f133 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:28:08 +0900 Subject: [PATCH 003/157] =?UTF-8?q?refact=20:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApiController.java | 6 ++---- .../core/domain/auth/service/AuthManager.java | 14 ++++++++++++++ .../core/domain/member/service/MemberService.java | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index 8f885acad..88068b3f1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -77,14 +77,12 @@ public ResponseEntity getTemporaryTokenByGoogle() { public ResponseEntity> reIssueTemporaryToken( @Valid @RequestBody final TemporaryTokenReIssueRequest request ) { - final Long id = memberService.findNotVerifiedMemberIdByAuthIdAndSocialType( - request.authId(), request.socialType() - ); + TemporaryTokenResponse temporaryToken = authManager.reIssueTemporaryToken(request.authId(), request.socialType()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - tokenService.createTemporaryToken(id) + temporaryToken ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index 573b0cfb8..a0e19c261 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -3,6 +3,9 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; +import site.timecapsulearchive.core.domain.member.entity.SocialType; +import site.timecapsulearchive.core.domain.member.service.MemberService; @Component @RequiredArgsConstructor @@ -11,6 +14,10 @@ public class AuthManager { private static final String KAKAO_AUTHORIZATION_ENDPOINT = "/auth/login/kakao"; private static final String GOOGLE_AUTHORIZATION_ENDPOINT = "/auth/login/google"; + private final MemberService memberService; + + private final TokenManager tokenManager; + public String getOAuth2KakaoUrl(final HttpServletRequest request) { final String baseUrl = request.getRequestURL().toString(); @@ -28,4 +35,11 @@ public String getOauth2GoogleUrl(HttpServletRequest request) { request.getContextPath() + GOOGLE_AUTHORIZATION_ENDPOINT ); } + + public TemporaryTokenResponse reIssueTemporaryToken(final String authId, + final SocialType socialType) { + Long notVerifiedMemberId = memberService.findNotVerifiedMemberIdBy(authId, socialType); + + return tokenManager.createTemporaryToken(notVerifiedMemberId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 2bf412d28..4c8931ed2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -105,7 +105,7 @@ public Long findVerifiedMemberIdByAuthIdAndSocialType( * @return 인증되지 않은 사용자의 아이디 * @throws AlreadyVerifiedException 이미 인증된 사용자인 경우 발생하는 예외 */ - public Long findNotVerifiedMemberIdByAuthIdAndSocialType( + public Long findNotVerifiedMemberIdBy( final String authId, final SocialType socialType ) { From b798c997396bd95d0ffe5be4536fb5b880bc4c7e Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:30:06 +0900 Subject: [PATCH 004/157] =?UTF-8?q?refact=20:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApiController.java | 4 +++- .../core/domain/auth/service/AuthManager.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index 88068b3f1..efd46c700 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -96,10 +96,12 @@ public ResponseEntity> reIssueTemporaryToken( public ResponseEntity> reIssueAccessToken( @Valid @RequestBody final TokenReIssueRequest request ) { + TokenResponse token = authManager.reIssueToken(request.refreshToken()); + return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - tokenService.reIssueToken(request.refreshToken()) + token ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index a0e19c261..93c36d5c4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; +import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.domain.member.service.MemberService; @@ -42,4 +43,8 @@ public TemporaryTokenResponse reIssueTemporaryToken(final String authId, return tokenManager.createTemporaryToken(notVerifiedMemberId); } + + public TokenResponse reIssueToken(String refreshToken) { + return tokenManager.reIssueToken(refreshToken); + } } From a160a35aca340e0f734a499dde6445426ba9a31a Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:31:37 +0900 Subject: [PATCH 005/157] =?UTF-8?q?refact=20:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EB=A1=9C=EC=A7=81=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApiController.java | 4 ++-- .../core/domain/auth/service/AuthManager.java | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index efd46c700..af0b9cbe0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -115,12 +115,12 @@ public ResponseEntity> reIssueAccessToken( public ResponseEntity> signUpWithSocialProvider( @Valid @RequestBody final SignUpRequest request ) { - final Long id = memberService.createMember(request.toDto()); + TemporaryTokenResponse temporaryToken = authManager.signUp(request.toDto()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - tokenService.createTemporaryToken(id) + temporaryToken ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index 93c36d5c4..2daae42f6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Component; import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; +import site.timecapsulearchive.core.domain.member.data.dto.SignUpRequestDto; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.domain.member.service.MemberService; @@ -47,4 +48,10 @@ public TemporaryTokenResponse reIssueTemporaryToken(final String authId, public TokenResponse reIssueToken(String refreshToken) { return tokenManager.reIssueToken(refreshToken); } + + public TemporaryTokenResponse signUp(SignUpRequestDto dto) { + Long createdMemberId = memberService.createMember(dto); + + return tokenManager.createTemporaryToken(createdMemberId); + } } From 3fc1414df08a16d519639383135cbcb90ff287ea Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:34:46 +0900 Subject: [PATCH 006/157] =?UTF-8?q?refact=20:=20final=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?,=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/api/AuthApiController.java | 16 ++++++++-------- .../core/domain/auth/service/AuthManager.java | 18 +++++++++++++----- .../domain/member/service/MemberService.java | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index af0b9cbe0..ff3e89391 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -43,7 +43,7 @@ public class AuthApiController implements AuthApi { @GetMapping(value = "/login/url/kakao", produces = {"application/json"}) @Override public ResponseEntity getOAuth2KakaoUrl(final HttpServletRequest request) { - String kakaoLoginUrl = authManager.getOAuth2KakaoUrl(request); + final String kakaoLoginUrl = authManager.getOAuth2KakaoUrl(request); return ResponseEntity.ok(OAuth2UriResponse.from(kakaoLoginUrl)); } @@ -51,7 +51,7 @@ public ResponseEntity getOAuth2KakaoUrl(final HttpServletRequ @GetMapping(value = "/login/url/google", produces = {"application/json"}) @Override public ResponseEntity getOAuth2GoogleUrl(final HttpServletRequest request) { - String googleLoginUrl = authManager.getOauth2GoogleUrl(request); + final String googleLoginUrl = authManager.getOauth2GoogleUrl(request); return ResponseEntity.ok(OAuth2UriResponse.from(googleLoginUrl)); } @@ -77,7 +77,8 @@ public ResponseEntity getTemporaryTokenByGoogle() { public ResponseEntity> reIssueTemporaryToken( @Valid @RequestBody final TemporaryTokenReIssueRequest request ) { - TemporaryTokenResponse temporaryToken = authManager.reIssueTemporaryToken(request.authId(), request.socialType()); + final TemporaryTokenResponse temporaryToken = authManager.reIssueTemporaryToken( + request.authId(), request.socialType()); return ResponseEntity.ok( ApiSpec.success( @@ -96,7 +97,7 @@ public ResponseEntity> reIssueTemporaryToken( public ResponseEntity> reIssueAccessToken( @Valid @RequestBody final TokenReIssueRequest request ) { - TokenResponse token = authManager.reIssueToken(request.refreshToken()); + final TokenResponse token = authManager.reIssueToken(request.refreshToken()); return ResponseEntity.ok( ApiSpec.success( @@ -115,7 +116,7 @@ public ResponseEntity> reIssueAccessToken( public ResponseEntity> signUpWithSocialProvider( @Valid @RequestBody final SignUpRequest request ) { - TemporaryTokenResponse temporaryToken = authManager.signUp(request.toDto()); + final TemporaryTokenResponse temporaryToken = authManager.signUp(request.toDto()); return ResponseEntity.ok( ApiSpec.success( @@ -133,13 +134,12 @@ public ResponseEntity> signUpWithSocialProvider( @Override public ResponseEntity> signInWithSocialProvider( @Valid @RequestBody final SignInRequest request) { - final Long memberId = memberService.findVerifiedMemberIdByAuthIdAndSocialType( - request.authId(), request.socialType()); + final TokenResponse token = authManager.signIn(request.authId(), request.socialType()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - tokenService.createNewToken(memberId) + token ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index 2daae42f6..e32b220bf 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -29,7 +29,7 @@ public String getOAuth2KakaoUrl(final HttpServletRequest request) { ); } - public String getOauth2GoogleUrl(HttpServletRequest request) { + public String getOauth2GoogleUrl(final HttpServletRequest request) { final String baseUrl = request.getRequestURL().toString(); return baseUrl.replace( @@ -40,18 +40,26 @@ public String getOauth2GoogleUrl(HttpServletRequest request) { public TemporaryTokenResponse reIssueTemporaryToken(final String authId, final SocialType socialType) { - Long notVerifiedMemberId = memberService.findNotVerifiedMemberIdBy(authId, socialType); + final Long notVerifiedMemberId = memberService.findNotVerifiedMemberIdBy(authId, + socialType); return tokenManager.createTemporaryToken(notVerifiedMemberId); } - public TokenResponse reIssueToken(String refreshToken) { + public TokenResponse reIssueToken(final String refreshToken) { return tokenManager.reIssueToken(refreshToken); } - public TemporaryTokenResponse signUp(SignUpRequestDto dto) { - Long createdMemberId = memberService.createMember(dto); + public TemporaryTokenResponse signUp(final SignUpRequestDto dto) { + final Long createdMemberId = memberService.createMember(dto); return tokenManager.createTemporaryToken(createdMemberId); } + + public TokenResponse signIn(final String authId, final SocialType socialType) { + final Long verifiedSocialMemberId = memberService.findVerifiedSocialMemberIdBy(authId, + socialType); + + return tokenManager.createNewToken(verifiedSocialMemberId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 4c8931ed2..889b1045e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -82,7 +82,7 @@ public MemberStatusResponse checkStatus( * @return 인증된 사용자의 아이디 * @throws NotVerifiedMemberException 인증되지 않은 사용자인 경우에 발생하는 예외 */ - public Long findVerifiedMemberIdByAuthIdAndSocialType( + public Long findVerifiedSocialMemberIdBy( final String authId, final SocialType socialType ) throws NotVerifiedMemberException { From da1cd0aa32712ee4d924c38987a22d4539d12ef8 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:43:51 +0900 Subject: [PATCH 007/157] =?UTF-8?q?refact=20:=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B8=EC=A6=9D=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EB=B3=80=EA=B2=BD,=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/api/AuthApiController.java | 10 ++++------ .../data/dto/VerificationMessageSendDto.java | 18 ++++++++++++++++++ .../core/domain/auth/service/AuthManager.java | 13 +++++++++++-- .../service/MessageVerificationService.java | 6 +++--- 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/VerificationMessageSendDto.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index ff3e89391..32f6866e5 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.domain.auth.data.dto.VerificationMessageSendDto; import site.timecapsulearchive.core.domain.auth.data.request.EmailSignInRequest; import site.timecapsulearchive.core.domain.auth.data.request.EmailSignUpRequest; import site.timecapsulearchive.core.domain.auth.data.request.SignInRequest; @@ -154,17 +155,14 @@ public ResponseEntity> sendVerification @AuthenticationPrincipal final Long memberId, @Valid @RequestBody final VerificationMessageSendRequest request ) { - final VerificationMessageSendResponse response = messageVerificationService.sendVerificationMessage( - memberId, - request.receiver(), - request.appHashKey() - ); + final VerificationMessageSendDto verificationMessageSendDto = authManager.sendVerificationMessage( + memberId, request.receiver(), request.appHashKey()); return ResponseEntity.accepted() .body( ApiSpec.success( SuccessCode.ACCEPTED, - response + verificationMessageSendDto.toResponse() ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/VerificationMessageSendDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/VerificationMessageSendDto.java new file mode 100644 index 000000000..2d6c33685 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/VerificationMessageSendDto.java @@ -0,0 +1,18 @@ +package site.timecapsulearchive.core.domain.auth.data.dto; + +import site.timecapsulearchive.core.domain.auth.data.response.VerificationMessageSendResponse; + +public record VerificationMessageSendDto( + Integer status, + String message +) { + + public static VerificationMessageSendDto success(final Integer status, + final String message) { + return new VerificationMessageSendDto(status, message); + } + + public VerificationMessageSendResponse toResponse() { + return new VerificationMessageSendResponse(status, message); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index e32b220bf..a08dbb37b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -3,6 +3,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import site.timecapsulearchive.core.domain.auth.data.dto.VerificationMessageSendDto; import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.member.data.dto.SignUpRequestDto; @@ -16,9 +17,9 @@ public class AuthManager { private static final String KAKAO_AUTHORIZATION_ENDPOINT = "/auth/login/kakao"; private static final String GOOGLE_AUTHORIZATION_ENDPOINT = "/auth/login/google"; - private final MemberService memberService; - private final TokenManager tokenManager; + private final MemberService memberService; + private final MessageVerificationService messageVerificationService; public String getOAuth2KakaoUrl(final HttpServletRequest request) { final String baseUrl = request.getRequestURL().toString(); @@ -62,4 +63,12 @@ public TokenResponse signIn(final String authId, final SocialType socialType) { return tokenManager.createNewToken(verifiedSocialMemberId); } + + public VerificationMessageSendDto sendVerificationMessage( + final Long memberId, + final String receiver, + final String appHashKey + ) { + return messageVerificationService.sendVerificationMessage(memberId, receiver, appHashKey); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java index 54e7a1afa..9641f5b8f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java @@ -6,8 +6,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import site.timecapsulearchive.core.domain.auth.data.dto.VerificationMessageSendDto; import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; -import site.timecapsulearchive.core.domain.auth.data.response.VerificationMessageSendResponse; import site.timecapsulearchive.core.domain.auth.exception.CertificationNumberNotFoundException; import site.timecapsulearchive.core.domain.auth.exception.CertificationNumberNotMatchException; import site.timecapsulearchive.core.domain.auth.repository.MessageAuthenticationCacheRepository; @@ -47,7 +47,7 @@ public class MessageVerificationService { * @param receiver 수신자 핸드폰 번호 * @param appHashKey 앱의 해시 키(메시지 자동 파싱) */ - public VerificationMessageSendResponse sendVerificationMessage( + public VerificationMessageSendDto sendVerificationMessage( final Long memberId, final String receiver, final String appHashKey @@ -61,7 +61,7 @@ public VerificationMessageSendResponse sendVerificationMessage( messageAuthenticationCacheRepository.save(memberId, encrypt, code); - return VerificationMessageSendResponse.success(apiResponse.resultCode(), + return VerificationMessageSendDto.success(apiResponse.resultCode(), apiResponse.message()); } From 83beaf3394ed8ab3d5c5287bbb6172c18871ae3c Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:48:53 +0900 Subject: [PATCH 008/157] =?UTF-8?q?refact=20:=20=EB=AC=B8=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=88=98=EC=A0=95,=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApiController.java | 9 +++------ .../core/domain/auth/service/AuthManager.java | 12 ++++++++++++ .../auth/service/MessageVerificationService.java | 7 ++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index 32f6866e5..c26efa6ea 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -177,16 +177,13 @@ public ResponseEntity> validVerificationMessage( @AuthenticationPrincipal final Long memberId, @Valid @RequestBody final VerificationNumberValidRequest request ) { - final TokenResponse response = messageVerificationService.validVerificationMessage( - memberId, - request.certificationNumber(), - request.receiver() - ); + TokenResponse token = authManager.validVerificationMessage(memberId, + request.certificationNumber(), request.receiver()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - response + token ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index a08dbb37b..3de695ff1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -71,4 +71,16 @@ public VerificationMessageSendDto sendVerificationMessage( ) { return messageVerificationService.sendVerificationMessage(memberId, receiver, appHashKey); } + + public TokenResponse validVerificationMessage( + final Long memberId, + final String certificationNumber, + final String receiver + ) { + Long verifiedMemberId = messageVerificationService.validVerificationMessage(memberId, + certificationNumber, + receiver); + + return tokenManager.createNewToken(verifiedMemberId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java index 9641f5b8f..201108796 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java @@ -7,7 +7,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import site.timecapsulearchive.core.domain.auth.data.dto.VerificationMessageSendDto; -import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.auth.exception.CertificationNumberNotFoundException; import site.timecapsulearchive.core.domain.auth.exception.CertificationNumberNotMatchException; import site.timecapsulearchive.core.domain.auth.repository.MessageAuthenticationCacheRepository; @@ -79,7 +78,7 @@ private String generateMessage(final String code, final String appHashKey) { + "타인 노출 금지"; } - public TokenResponse validVerificationMessage( + public Long validVerificationMessage( final Long memberId, final String certificationNumber, final String receiver @@ -95,9 +94,7 @@ public TokenResponse validVerificationMessage( throw new CertificationNumberNotMatchException(); } - final Long verifiedMemberId = updateToVerifiedMember(memberId, plain); - - return tokenManager.createNewToken(verifiedMemberId); + return updateToVerifiedMember(memberId, plain); } private boolean isNotMatch(final String certificationNumber, From 4656dc77f7c573f5b185c234b5da3213ac8dd45c Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:52:24 +0900 Subject: [PATCH 009/157] =?UTF-8?q?refact=20:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApiController.java | 4 ++-- .../core/domain/auth/service/AuthManager.java | 6 ++++++ .../core/domain/member/service/MemberService.java | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index c26efa6ea..a9df43079 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -197,13 +197,13 @@ public ResponseEntity> validVerificationMessage( public ResponseEntity> signUpWithEmail( @Valid @RequestBody final EmailSignUpRequest request ) { - final Long id = memberService.createMemberWithEmailAndPassword(request.email(), + TemporaryTokenResponse temporaryToken = authManager.signUpWithEmail(request.email(), request.password()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - tokenService.createTemporaryToken(id) + temporaryToken ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index 3de695ff1..211fce9c3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -83,4 +83,10 @@ public TokenResponse validVerificationMessage( return tokenManager.createNewToken(verifiedMemberId); } + + public TemporaryTokenResponse signUpWithEmail(final String email, final String password) { + Long createdMemberId = memberService.createMemberWithEmail(email, password); + + return tokenManager.createTemporaryToken(createdMemberId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 889b1045e..ebed60bac 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -162,7 +162,7 @@ public MemberNotificationSliceResponse findNotificationSliceByMemberId( } @Transactional - public Long createMemberWithEmailAndPassword(final String email, final String password) { + public Long createMemberWithEmail(final String email, final String password) { final String encodedPassword = passwordEncoder.encode(password); final Member member = memberMapper.createMemberWithEmail(email, encodedPassword); From d9287605b2182ada010d0ca8a4cddc28102eed82 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:54:18 +0900 Subject: [PATCH 010/157] =?UTF-8?q?refact=20:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApiController.java | 4 ++-- .../core/domain/auth/service/AuthManager.java | 6 ++++++ .../core/domain/member/service/MemberService.java | 2 +- .../domain/member/service/MemberServiceTest.java | 14 +++++++------- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index a9df43079..008ae5b63 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -217,13 +217,13 @@ public ResponseEntity> signUpWithEmail( public ResponseEntity> signInWithEmail( @Valid @RequestBody final EmailSignInRequest request ) { - final Long id = memberService.findVerifiedMemberIdByEmailAndPassword(request.email(), + TokenResponse token = authManager.signInWithEmail(request.email(), request.password()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - tokenService.createNewToken(id) + token ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index 211fce9c3..1c0e676a3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -89,4 +89,10 @@ public TemporaryTokenResponse signUpWithEmail(final String email, final String p return tokenManager.createTemporaryToken(createdMemberId); } + + public TokenResponse signInWithEmail(final String email, final String password) { + Long verifiedEmailMemberId = memberService.findVerifiedEmailMemberIdBy(email, password); + + return tokenManager.createNewToken(verifiedEmailMemberId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index ebed60bac..f454481d4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -179,7 +179,7 @@ public Long createMemberWithEmail(final String email, final String password) { return savedMember.getId(); } - public Long findVerifiedMemberIdByEmailAndPassword(final String email, final String password) { + public Long findVerifiedEmailMemberIdBy(final String email, final String password) { final EmailVerifiedCheckDto dto = memberRepository.findEmailVerifiedCheckDtoByEmail( email) .orElseThrow(MemberNotFoundException::new); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java index dd2095c84..bebf72b49 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java @@ -43,7 +43,7 @@ class MemberServiceTest { //when, then assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password)) + () -> memberService.findVerifiedEmailMemberIdBy(email, password)) .isExactlyInstanceOf(MemberNotFoundException.class); } @@ -56,7 +56,7 @@ class MemberServiceTest { .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); //when - Long id = memberService.findVerifiedMemberIdByEmailAndPassword( + Long id = memberService.findVerifiedEmailMemberIdBy( email, password); //then @@ -85,7 +85,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //when, then assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password)) + () -> memberService.findVerifiedEmailMemberIdBy(email, password)) .isExactlyInstanceOf(NotVerifiedMemberException.class); } @@ -99,7 +99,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //when, then assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email + "trash", password)) + () -> memberService.findVerifiedEmailMemberIdBy(email + "trash", password)) .isExactlyInstanceOf(CredentialsNotMatchedException.class); } @@ -113,7 +113,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //when, then assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password + "trash")) + () -> memberService.findVerifiedEmailMemberIdBy(email, password + "trash")) .isExactlyInstanceOf(CredentialsNotMatchedException.class); } @@ -127,7 +127,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //when, then assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password)) + () -> memberService.findVerifiedEmailMemberIdBy(email, password)) .isExactlyInstanceOf(CredentialsNotMatchedException.class); } @@ -141,7 +141,7 @@ private Optional getVerifiedCheckDto(Boolean isVerified, //when, then assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email + "password", + () -> memberService.findVerifiedEmailMemberIdBy(email + "password", password + "trash")) .isExactlyInstanceOf(CredentialsNotMatchedException.class); } From 9506465fbed72e12280d8e54c49ad9f2c4248a4c Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 22:54:56 +0900 Subject: [PATCH 011/157] =?UTF-8?q?fix=20:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApiController.java | 4 ---- .../core/domain/auth/service/MessageVerificationService.java | 1 - 2 files changed, 5 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index 008ae5b63..af91d638c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -36,10 +36,6 @@ public class AuthApiController implements AuthApi { private final AuthManager authManager; - private final TokenManager tokenService; - private final MessageVerificationService messageVerificationService; - private final MemberService memberService; - @GetMapping(value = "/login/url/kakao", produces = {"application/json"}) @Override diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java index 201108796..9b2c5fa89 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java @@ -34,7 +34,6 @@ public class MessageVerificationService { private final SmsApiManager smsApiManager; private final MemberRepository memberRepository; private final MemberTemporaryRepository memberTemporaryRepository; - private final TokenManager tokenManager; private final AESEncryptionManager aesEncryptionManager; private final HashEncryptionManager hashEncryptionManager; From 487dabf638602cac19307c0a2ca42bced2a1ff80 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Thu, 20 Jun 2024 23:04:57 +0900 Subject: [PATCH 012/157] =?UTF-8?q?refact=20:=20dto=20to=20response?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/api/AuthApiController.java | 30 ++++++++++--------- .../auth/data/dto/TemporaryTokenDto.java | 23 ++++++++++++++ .../core/domain/auth/data/dto/TokenDto.java | 29 ++++++++++++++++++ .../data/response/TemporaryTokenResponse.java | 9 ------ .../auth/data/response/TokenResponse.java | 13 -------- .../core/domain/auth/service/AuthManager.java | 18 +++++------ .../domain/auth/service/TokenManager.java | 14 +++++---- 7 files changed, 85 insertions(+), 51 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TemporaryTokenDto.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TokenDto.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index af91d638c..c23d94554 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -10,6 +10,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; +import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; import site.timecapsulearchive.core.domain.auth.data.dto.VerificationMessageSendDto; import site.timecapsulearchive.core.domain.auth.data.request.EmailSignInRequest; import site.timecapsulearchive.core.domain.auth.data.request.EmailSignUpRequest; @@ -74,13 +76,13 @@ public ResponseEntity getTemporaryTokenByGoogle() { public ResponseEntity> reIssueTemporaryToken( @Valid @RequestBody final TemporaryTokenReIssueRequest request ) { - final TemporaryTokenResponse temporaryToken = authManager.reIssueTemporaryToken( + final TemporaryTokenDto temporaryToken = authManager.reIssueTemporaryToken( request.authId(), request.socialType()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - temporaryToken + temporaryToken.toResponse() ) ); } @@ -94,12 +96,12 @@ public ResponseEntity> reIssueTemporaryToken( public ResponseEntity> reIssueAccessToken( @Valid @RequestBody final TokenReIssueRequest request ) { - final TokenResponse token = authManager.reIssueToken(request.refreshToken()); + final TokenDto token = authManager.reIssueToken(request.refreshToken()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - token + token.toResponse() ) ); } @@ -113,12 +115,12 @@ public ResponseEntity> reIssueAccessToken( public ResponseEntity> signUpWithSocialProvider( @Valid @RequestBody final SignUpRequest request ) { - final TemporaryTokenResponse temporaryToken = authManager.signUp(request.toDto()); + final TemporaryTokenDto temporaryToken = authManager.signUp(request.toDto()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - temporaryToken + temporaryToken.toResponse() ) ); } @@ -131,12 +133,12 @@ public ResponseEntity> signUpWithSocialProvider( @Override public ResponseEntity> signInWithSocialProvider( @Valid @RequestBody final SignInRequest request) { - final TokenResponse token = authManager.signIn(request.authId(), request.socialType()); + final TokenDto token = authManager.signIn(request.authId(), request.socialType()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - token + token.toResponse() ) ); } @@ -173,13 +175,13 @@ public ResponseEntity> validVerificationMessage( @AuthenticationPrincipal final Long memberId, @Valid @RequestBody final VerificationNumberValidRequest request ) { - TokenResponse token = authManager.validVerificationMessage(memberId, + TokenDto token = authManager.validVerificationMessage(memberId, request.certificationNumber(), request.receiver()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - token + token.toResponse() ) ); } @@ -193,13 +195,13 @@ public ResponseEntity> validVerificationMessage( public ResponseEntity> signUpWithEmail( @Valid @RequestBody final EmailSignUpRequest request ) { - TemporaryTokenResponse temporaryToken = authManager.signUpWithEmail(request.email(), + TemporaryTokenDto temporaryToken = authManager.signUpWithEmail(request.email(), request.password()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - temporaryToken + temporaryToken.toResponse() ) ); } @@ -213,13 +215,13 @@ public ResponseEntity> signUpWithEmail( public ResponseEntity> signInWithEmail( @Valid @RequestBody final EmailSignInRequest request ) { - TokenResponse token = authManager.signInWithEmail(request.email(), + TokenDto token = authManager.signInWithEmail(request.email(), request.password()); return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - token + token.toResponse() ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TemporaryTokenDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TemporaryTokenDto.java new file mode 100644 index 000000000..ed39a3972 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TemporaryTokenDto.java @@ -0,0 +1,23 @@ +package site.timecapsulearchive.core.domain.auth.data.dto; + +import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; + +public record TemporaryTokenDto( + String temporaryAccessToken, + long expiresIn +) { + + public static TemporaryTokenDto create( + final String temporaryAccessToken, + final long expiresIn + ) { + return new TemporaryTokenDto( + temporaryAccessToken, + expiresIn + ); + } + + public TemporaryTokenResponse toResponse() { + return new TemporaryTokenResponse(temporaryAccessToken, expiresIn); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TokenDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TokenDto.java new file mode 100644 index 000000000..a93a037f3 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/dto/TokenDto.java @@ -0,0 +1,29 @@ +package site.timecapsulearchive.core.domain.auth.data.dto; + +import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; + +public record TokenDto( + String accessToken, + String refreshToken, + long expiresIn, + long refreshTokenExpiresIn +) { + + public static TokenDto create( + String accessToken, + String refreshToken, + long expiresIn, + long refreshTokenExpiresIn + ) { + return new TokenDto( + accessToken, + refreshToken, + expiresIn, + refreshTokenExpiresIn + ); + } + + public TokenResponse toResponse() { + return new TokenResponse(accessToken, refreshToken, expiresIn, refreshTokenExpiresIn); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TemporaryTokenResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TemporaryTokenResponse.java index 382df5fcd..0ec76172f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TemporaryTokenResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TemporaryTokenResponse.java @@ -12,13 +12,4 @@ public record TemporaryTokenResponse( long expiresIn ) { - public static TemporaryTokenResponse create( - String temporaryAccessToken, - long expiresIn - ) { - return new TemporaryTokenResponse( - temporaryAccessToken, - expiresIn - ); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TokenResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TokenResponse.java index 6a7f2090f..0b8a8bcaa 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TokenResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/response/TokenResponse.java @@ -18,17 +18,4 @@ public record TokenResponse( long refreshTokenExpiresIn ) { - public static TokenResponse create( - String accessToken, - String refreshToken, - long expiresIn, - long refreshTokenExpiresIn - ) { - return new TokenResponse( - accessToken, - refreshToken, - expiresIn, - refreshTokenExpiresIn - ); - } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index 1c0e676a3..da72345b9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -3,9 +3,9 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; +import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; import site.timecapsulearchive.core.domain.auth.data.dto.VerificationMessageSendDto; -import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; -import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.member.data.dto.SignUpRequestDto; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.domain.member.service.MemberService; @@ -39,7 +39,7 @@ public String getOauth2GoogleUrl(final HttpServletRequest request) { ); } - public TemporaryTokenResponse reIssueTemporaryToken(final String authId, + public TemporaryTokenDto reIssueTemporaryToken(final String authId, final SocialType socialType) { final Long notVerifiedMemberId = memberService.findNotVerifiedMemberIdBy(authId, socialType); @@ -47,17 +47,17 @@ public TemporaryTokenResponse reIssueTemporaryToken(final String authId, return tokenManager.createTemporaryToken(notVerifiedMemberId); } - public TokenResponse reIssueToken(final String refreshToken) { + public TokenDto reIssueToken(final String refreshToken) { return tokenManager.reIssueToken(refreshToken); } - public TemporaryTokenResponse signUp(final SignUpRequestDto dto) { + public TemporaryTokenDto signUp(final SignUpRequestDto dto) { final Long createdMemberId = memberService.createMember(dto); return tokenManager.createTemporaryToken(createdMemberId); } - public TokenResponse signIn(final String authId, final SocialType socialType) { + public TokenDto signIn(final String authId, final SocialType socialType) { final Long verifiedSocialMemberId = memberService.findVerifiedSocialMemberIdBy(authId, socialType); @@ -72,7 +72,7 @@ public VerificationMessageSendDto sendVerificationMessage( return messageVerificationService.sendVerificationMessage(memberId, receiver, appHashKey); } - public TokenResponse validVerificationMessage( + public TokenDto validVerificationMessage( final Long memberId, final String certificationNumber, final String receiver @@ -84,13 +84,13 @@ public TokenResponse validVerificationMessage( return tokenManager.createNewToken(verifiedMemberId); } - public TemporaryTokenResponse signUpWithEmail(final String email, final String password) { + public TemporaryTokenDto signUpWithEmail(final String email, final String password) { Long createdMemberId = memberService.createMemberWithEmail(email, password); return tokenManager.createTemporaryToken(createdMemberId); } - public TokenResponse signInWithEmail(final String email, final String password) { + public TokenDto signInWithEmail(final String email, final String password) { Long verifiedEmailMemberId = memberService.findVerifiedEmailMemberIdBy(email, password); return tokenManager.createNewToken(verifiedEmailMemberId); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java index 1e3240f3d..ca78b0899 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java @@ -5,6 +5,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import site.timecapsulearchive.core.domain.auth.data.dto.MemberInfo; +import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; +import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.auth.exception.AlreadyReIssuedTokenException; @@ -26,15 +28,15 @@ public class TokenManager { * @param memberId 액세스 토큰 클레임 값에 넣을 사용자 아이디 * @return 토큰 응답(액세스 토큰, 리프레시 토큰, 액세스 토큰 만료일, 리프레시 토큰 만료일) */ - public TokenResponse createNewToken(final Long memberId) { + public TokenDto createNewToken(final Long memberId) { final String key = String.valueOf(UUID.randomUUID()); memberInfoCacheRepository.save(key, MemberInfo.from(memberId)); return createTokenResponse(memberId, key); } - private TokenResponse createTokenResponse(final Long memberId, final String key) { - return TokenResponse.create( + private TokenDto createTokenResponse(final Long memberId, final String key) { + return TokenDto.create( jwtFactory.createAccessToken(memberId), jwtFactory.createRefreshToken(key), jwtFactory.getExpiresIn(), @@ -48,8 +50,8 @@ private TokenResponse createTokenResponse(final Long memberId, final String key) * @param memberId 임시 인증 토큰 클레임 값에 사용할 사용자 아이디 * @return 임시 인증 토큰 응답(임시 인증 토큰, 임시 인증 토큰 만료일) */ - public TemporaryTokenResponse createTemporaryToken(final Long memberId) { - return TemporaryTokenResponse.create( + public TemporaryTokenDto createTemporaryToken(final Long memberId) { + return TemporaryTokenDto.create( jwtFactory.createTemporaryAccessToken(memberId), jwtFactory.getTemporaryTokenExpiresIn() ); @@ -61,7 +63,7 @@ public TemporaryTokenResponse createTemporaryToken(final Long memberId) { * @param refreshToken 리프레시 토큰 * @return 토큰 응답(액세스 토큰, 리프레시 토큰, 액세스 토큰 만료일, 리프레시 토큰 만료일) */ - public TokenResponse reIssueToken(final String refreshToken) { + public TokenDto reIssueToken(final String refreshToken) { final TokenParseResult tokenParseResult = jwtFactory.parse( refreshToken, List.of(TokenType.REFRESH) From 31f451564f0200419ca975241f65be894c6536f2 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Fri, 21 Jun 2024 19:04:05 +0900 Subject: [PATCH 013/157] =?UTF-8?q?fix=20:=20refresh=20token=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - key:memberInfo -> memberId:refreshToken 변경 --- .../repository/MemberInfoCacheRepository.java | 44 ++++--------------- .../domain/auth/service/TokenManager.java | 40 +++++++++++------ .../core/global/security/jwt/JwtFactory.java | 4 +- 3 files changed, 38 insertions(+), 50 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/MemberInfoCacheRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/MemberInfoCacheRepository.java index 48e0731cd..6d5c16601 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/MemberInfoCacheRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/MemberInfoCacheRepository.java @@ -3,57 +3,31 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; -import site.timecapsulearchive.core.domain.auth.data.dto.MemberInfo; @Repository @RequiredArgsConstructor public class MemberInfoCacheRepository { private static final int MAXIMUM_REFRESH_TOKEN_EXPIRES_IN_DAY = 30; - private static final String PREFIX = "memberInfo:"; + private static final String PREFIX = "refreshToken-memberId:"; - private final RedisTemplate redisTemplate; + private final StringRedisTemplate redisTemplate; - /** - * 키와 {@code memberInfo}를 받아서 캐시에 저장 - * - * @param key 캐시에 저장할 키, UUID string - * @param memberInfo 저장할 사용자 정보 - */ public void save( - final String key, - final MemberInfo memberInfo + final Long memberId, + final String refreshToken ) { redisTemplate.opsForValue().set( - PREFIX + key, - memberInfo, + PREFIX + memberId, + refreshToken, MAXIMUM_REFRESH_TOKEN_EXPIRES_IN_DAY, TimeUnit.DAYS ); } - /** - * 사용자 정보 조회 - * - * @param infoKey 캐시에 저장된 키, UUID string - * @return {@code Optional} 사용자 정보 - */ - public Optional findMemberInfoByInfoKey(final String infoKey) { - return Optional.ofNullable(redisTemplate.opsForValue().get(PREFIX + infoKey)); - } - - /** - * 삭제 로직 대신 키의 이름을 변경, O(1) 소요 - * - * @param oldKey 이전 키 - * @param newKey 새로운 키 - */ - public void rename( - final String oldKey, - final String newKey - ) { - redisTemplate.rename(PREFIX + oldKey, PREFIX + newKey); + public Optional findRefreshTokenByMemberId(final Long memberId) { + return Optional.ofNullable(redisTemplate.opsForValue().get(PREFIX + memberId)); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java index ca78b0899..43fda65d7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java @@ -4,13 +4,11 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import site.timecapsulearchive.core.domain.auth.data.dto.MemberInfo; import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; -import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; -import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.auth.exception.AlreadyReIssuedTokenException; import site.timecapsulearchive.core.domain.auth.repository.MemberInfoCacheRepository; +import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; import site.timecapsulearchive.core.global.security.jwt.JwtFactory; import site.timecapsulearchive.core.global.security.jwt.TokenParseResult; import site.timecapsulearchive.core.global.security.jwt.TokenType; @@ -29,16 +27,26 @@ public class TokenManager { * @return 토큰 응답(액세스 토큰, 리프레시 토큰, 액세스 토큰 만료일, 리프레시 토큰 만료일) */ public TokenDto createNewToken(final Long memberId) { - final String key = String.valueOf(UUID.randomUUID()); - memberInfoCacheRepository.save(key, MemberInfo.from(memberId)); + /** + * 1. 회원 아이디로 리프레시 토큰 생성 + * 2. 생성된 리프레시 토큰과 회원 아이디로 "1:ejfkdsvxlcvmlxc"로 redis에 저장한다. + * 3. 생성된 토큰을 반환한다. + * + * 1. 회원 아이디로 리프레시 토큰을 찾는다 + * 2. 찾은 리프레시 토큰과 요청 받은 리프레시 토큰을 비교한다. + * 3. 일치하면 새로운 리프레시 토큰과 액세스 토큰을 반환한다 + * 4. 일치하지 않으면 예외를 반환한다. + */ + String refreshToken = jwtFactory.createRefreshToken(memberId); + memberInfoCacheRepository.save(memberId, refreshToken); - return createTokenResponse(memberId, key); + return createToken(memberId, refreshToken); } - private TokenDto createTokenResponse(final Long memberId, final String key) { + private TokenDto createToken(final Long memberId, final String refreshToken) { return TokenDto.create( jwtFactory.createAccessToken(memberId), - jwtFactory.createRefreshToken(key), + refreshToken, jwtFactory.getExpiresIn(), jwtFactory.getRefreshTokenExpiresIn() ); @@ -68,13 +76,19 @@ public TokenDto reIssueToken(final String refreshToken) { refreshToken, List.of(TokenType.REFRESH) ); - final MemberInfo memberInfo = memberInfoCacheRepository.findMemberInfoByInfoKey( - tokenParseResult.subject()) + + Long memberId = Long.valueOf(tokenParseResult.subject()); + final String foundRefreshToken = memberInfoCacheRepository.findRefreshTokenByMemberId( + memberId) .orElseThrow(AlreadyReIssuedTokenException::new); - final String newKey = String.valueOf(UUID.randomUUID()); - memberInfoCacheRepository.rename(tokenParseResult.subject(), newKey); + if (!refreshToken.equals(foundRefreshToken)) { + throw new InvalidTokenException(); + } + + String newRefreshToken = jwtFactory.createRefreshToken(memberId); + memberInfoCacheRepository.save(memberId, newRefreshToken); - return createTokenResponse(memberInfo.memberId(), newKey); + return createToken(memberId, refreshToken); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java index 143635f36..2b307fcae 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java @@ -57,13 +57,13 @@ public String createAccessToken(final Long memberId) { * @param memberInfoKey 사용자 식별자 * @return 리프레시 토큰 */ - public String createRefreshToken(final String memberInfoKey) { + public String createRefreshToken(final Long memberId) { Date now = new Date(); Date validity = new Date(now.getTime() + refreshTokenValidityMs); return Jwts.builder() .setIssuer(ISSUER) - .setSubject(memberInfoKey) + .setSubject(String.valueOf(memberId)) .setExpiration(validity) .claim(TOKEN_TYPE_CLAIM_NAME, TokenType.REFRESH.name()) .signWith(key, SignatureAlgorithm.HS256) From 519d67038d6aaf3ce8000440c265eaf50f1d0cb8 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Fri, 21 Jun 2024 23:20:36 +0900 Subject: [PATCH 014/157] =?UTF-8?q?fix=20:=20repository=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84,=20PREFIX=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - repository 용도 변경으로 이름 변경 - PREFIX 변경, 용도_키_값 --- ...pository.java => RefreshTokenCacheRepository.java} | 4 ++-- .../core/domain/auth/service/TokenManager.java | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) rename backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/{MemberInfoCacheRepository.java => RefreshTokenCacheRepository.java} (88%) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/MemberInfoCacheRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/RefreshTokenCacheRepository.java similarity index 88% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/MemberInfoCacheRepository.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/RefreshTokenCacheRepository.java index 6d5c16601..517f278e9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/MemberInfoCacheRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/RefreshTokenCacheRepository.java @@ -8,10 +8,10 @@ @Repository @RequiredArgsConstructor -public class MemberInfoCacheRepository { +public class RefreshTokenCacheRepository { private static final int MAXIMUM_REFRESH_TOKEN_EXPIRES_IN_DAY = 30; - private static final String PREFIX = "refreshToken-memberId:"; + private static final String PREFIX = "reIssue_memberId_refreshToken:"; private final StringRedisTemplate redisTemplate; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java index 43fda65d7..b2580a5bb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java @@ -1,13 +1,12 @@ package site.timecapsulearchive.core.domain.auth.service; import java.util.List; -import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; import site.timecapsulearchive.core.domain.auth.exception.AlreadyReIssuedTokenException; -import site.timecapsulearchive.core.domain.auth.repository.MemberInfoCacheRepository; +import site.timecapsulearchive.core.domain.auth.repository.RefreshTokenCacheRepository; import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; import site.timecapsulearchive.core.global.security.jwt.JwtFactory; import site.timecapsulearchive.core.global.security.jwt.TokenParseResult; @@ -18,7 +17,7 @@ public class TokenManager { private final JwtFactory jwtFactory; - private final MemberInfoCacheRepository memberInfoCacheRepository; + private final RefreshTokenCacheRepository refreshTokenCacheRepository; /** * 새로운 액세스 토큰과 리프레시 토큰을 발급한다. - 액세스 토큰(멤버 아이디) - 리프레시 토큰(데이터베이스에 저장된 사용자 식별자) @@ -38,7 +37,7 @@ public TokenDto createNewToken(final Long memberId) { * 4. 일치하지 않으면 예외를 반환한다. */ String refreshToken = jwtFactory.createRefreshToken(memberId); - memberInfoCacheRepository.save(memberId, refreshToken); + refreshTokenCacheRepository.save(memberId, refreshToken); return createToken(memberId, refreshToken); } @@ -78,7 +77,7 @@ public TokenDto reIssueToken(final String refreshToken) { ); Long memberId = Long.valueOf(tokenParseResult.subject()); - final String foundRefreshToken = memberInfoCacheRepository.findRefreshTokenByMemberId( + final String foundRefreshToken = refreshTokenCacheRepository.findRefreshTokenByMemberId( memberId) .orElseThrow(AlreadyReIssuedTokenException::new); @@ -87,7 +86,7 @@ public TokenDto reIssueToken(final String refreshToken) { } String newRefreshToken = jwtFactory.createRefreshToken(memberId); - memberInfoCacheRepository.save(memberId, newRefreshToken); + refreshTokenCacheRepository.save(memberId, newRefreshToken); return createToken(memberId, refreshToken); } From d95f88ade25398af670e24c77b735dc62668ab70 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 22 Jun 2024 12:04:47 +0900 Subject: [PATCH 015/157] =?UTF-8?q?fix=20:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/service/TokenManager.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java index b2580a5bb..1af838fc9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java @@ -26,16 +26,6 @@ public class TokenManager { * @return 토큰 응답(액세스 토큰, 리프레시 토큰, 액세스 토큰 만료일, 리프레시 토큰 만료일) */ public TokenDto createNewToken(final Long memberId) { - /** - * 1. 회원 아이디로 리프레시 토큰 생성 - * 2. 생성된 리프레시 토큰과 회원 아이디로 "1:ejfkdsvxlcvmlxc"로 redis에 저장한다. - * 3. 생성된 토큰을 반환한다. - * - * 1. 회원 아이디로 리프레시 토큰을 찾는다 - * 2. 찾은 리프레시 토큰과 요청 받은 리프레시 토큰을 비교한다. - * 3. 일치하면 새로운 리프레시 토큰과 액세스 토큰을 반환한다 - * 4. 일치하지 않으면 예외를 반환한다. - */ String refreshToken = jwtFactory.createRefreshToken(memberId); refreshTokenCacheRepository.save(memberId, refreshToken); From 3e785823c3e047c384a6528d9c7c3f0ea7761083 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sat, 22 Jun 2024 12:04:58 +0900 Subject: [PATCH 016/157] =?UTF-8?q?fix=20:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/global/security/jwt/JwtFactory.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java index 2b307fcae..1e1709913 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java @@ -74,7 +74,7 @@ public String createRefreshToken(final Long memberId) { * 사용자 아이디를 받아서 임시 토큰 (1시간) 토큰 반환 * * @param memberId 사용자 아이디 - * @return 리프레시 토큰 + * @return 임시 인증 토큰 */ public String createTemporaryAccessToken(final Long memberId) { Date now = new Date(); @@ -93,19 +93,19 @@ public String createTemporaryAccessToken(final Long memberId) { * 토큰과 토큰 타입으로 토큰의 사용자 식별자와 타입 추출 * * @param token 토큰 - * @return 사용자 식별자 + * @return 사용자 식별자, 토큰 타입 */ - public TokenParseResult parse(final String token, final List tokenTypes) { + public TokenParseResult parse(final String token, final List acceptableTokenTypes) { try { Claims claims = jwtParser() .parseClaimsJws(token) .getBody(); - validTokenType(tokenTypes, claims); + TokenType tokenType = getAcceptableTokenType(acceptableTokenTypes, claims); return TokenParseResult.of( claims.getSubject(), - TokenType.valueOf(claims.get(TOKEN_TYPE_CLAIM_NAME, String.class)) + tokenType ); } catch (final JwtException | IllegalArgumentException e) { throw new InvalidTokenException(e); @@ -118,20 +118,24 @@ private JwtParser jwtParser() { .build(); } - private void validTokenType(final List tokenTypes, final Claims claims) { - TokenType tokenType = TokenType.valueOf( - claims.get(TOKEN_TYPE_CLAIM_NAME, String.class) - ); + private TokenType getAcceptableTokenType(List tokenTypes, Claims claims) { + String extractTokenType = claims.get(TOKEN_TYPE_CLAIM_NAME, String.class); + if (extractTokenType == null || extractTokenType.isBlank()) { + throw new IllegalArgumentException(); + } + TokenType tokenType = TokenType.valueOf(extractTokenType); if (!tokenTypes.contains(tokenType)) { throw new JwtException("허용되지 않는 JWT 토큰 타입입니다."); } + return tokenType; } /** * 토큰을 파싱해서 올바른 토큰인지 확인 * * @param token 검증할 토큰 + * @throws InvalidTokenException 토큰이 유효하지 않은 경우 */ public void validate(final String token) { try { From c867aa7ddf4930225f08b63c0baf1689f2850746 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 23 Jun 2024 10:13:04 +0900 Subject: [PATCH 017/157] =?UTF-8?q?test=20:=20jwt,=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - jwt filter, provider 테스트 - token factory 테스트 - 인증 관련 service, manager 테스트 --- .../config/TestMockMvcSecurityConfig.java | 95 +++++++++ .../common/controller/AuthTestController.java | 18 ++ .../common/dependency/UnitTestDependency.java | 32 ++- .../domain/MemberTemporaryFixture.java | 18 ++ .../common/fixture/security/TokenFixture.java | 100 ++++++++++ .../MessageVerificationServiceTest.java | 128 ++++++++++++ .../domain/auth/service/TokenManagerTest.java | 100 ++++++++++ .../jwt/JwtAuthenticationFilterTest.java | 146 ++++++++++++++ .../jwt/JwtAuthenticationProviderTest.java | 122 ++++++++++++ .../global/security/jwt/JwtFactoryTest.java | 188 ++++++++++++++++++ 10 files changed, 938 insertions(+), 9 deletions(-) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/controller/AuthTestController.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/security/TokenFixture.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationServiceTest.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilterTest.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java new file mode 100644 index 000000000..c5dcbe1e7 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java @@ -0,0 +1,95 @@ +package site.timecapsulearchive.core.common.config; + +import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.NegatedRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatchers; +import site.timecapsulearchive.core.common.dependency.UnitTestDependency; +import site.timecapsulearchive.core.domain.member.entity.Role; +import site.timecapsulearchive.core.global.api.limit.ApiLimitCheckInterceptor; +import site.timecapsulearchive.core.global.api.limit.ApiLimitProperties; +import site.timecapsulearchive.core.global.api.limit.ApiUsageCacheRepository; +import site.timecapsulearchive.core.global.config.security.CommonSecurityDsl; +import site.timecapsulearchive.core.global.config.security.JwtDsl; +import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationFilter; +import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationProvider; + +@EnableWebSecurity +@TestConfiguration +public class TestMockMvcSecurityConfig { + + @Bean + public SecurityFilterChain filterChainWithJwt(final HttpSecurity http) throws Exception { + http.apply( + CommonSecurityDsl.commonSecurityDsl() + ); + + http + .securityMatchers( + c -> c.requestMatchers(new NegatedRequestMatcher(antMatcher("/auth/login/**"))) + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers(notRequireAuthenticationMatcher()).permitAll() + .requestMatchers( + "/auth/verification/**", + "/temporary-token/re-issue" + ).hasRole(Role.TEMPORARY.name()) + .anyRequest().hasRole(Role.USER.name()) + ); + + http.apply( + JwtDsl.jwtDsl( + jwtAuthenticationProvider(), + new ObjectMapper(), + notRequireAuthenticationMatcher() + ) + ); + + return http.build(); + } + + private RequestMatcher notRequireAuthenticationMatcher() { + return RequestMatchers.anyOf(antMatcher(HttpMethod.GET, "/pass")); + } + + @Bean + public JwtAuthenticationProvider jwtAuthenticationProvider() { + return new JwtAuthenticationProvider(UnitTestDependency.jwtFactory()); + } + + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(authenticationManager(), new ObjectMapper(), + notRequireAuthenticationMatcher()); + } + + @Bean + public AuthenticationManager authenticationManager() { + return new ProviderManager(jwtAuthenticationProvider()); + } + + @Bean + public ApiLimitCheckInterceptor interceptor() { + return new ApiLimitCheckInterceptor(apiLimitProperties(), repository()); + } + + @Bean + public ApiLimitProperties apiLimitProperties() { + return new ApiLimitProperties(10); + } + + @Bean + public ApiUsageCacheRepository repository() { + return new ApiUsageCacheRepository(null); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/controller/AuthTestController.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/controller/AuthTestController.java new file mode 100644 index 000000000..a09103ccc --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/controller/AuthTestController.java @@ -0,0 +1,18 @@ +package site.timecapsulearchive.core.common.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class AuthTestController { + + @GetMapping("/pass") + public String test() { + return "pass"; + } + + @GetMapping("/not-pass") + public String notPass() { + return "not-pass"; + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java index ca77597cb..c4fca06c6 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java @@ -1,25 +1,23 @@ package site.timecapsulearchive.core.common.dependency; import org.opengis.referencing.FactoryException; +import site.timecapsulearchive.core.global.config.security.JwtProperties; import site.timecapsulearchive.core.global.geography.GeoTransformConfig; import site.timecapsulearchive.core.global.geography.GeoTransformManager; +import site.timecapsulearchive.core.global.security.encryption.AESEncryptionManager; +import site.timecapsulearchive.core.global.security.encryption.AESKeyProperties; import site.timecapsulearchive.core.global.security.encryption.HashEncryptionManager; import site.timecapsulearchive.core.global.security.encryption.HashProperties; -import site.timecapsulearchive.core.infra.s3.config.S3Config; -import site.timecapsulearchive.core.infra.s3.config.S3Properties; -import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; +import site.timecapsulearchive.core.global.security.jwt.JwtFactory; +import site.timecapsulearchive.core.infra.sms.data.response.SmsApiResponse; +import site.timecapsulearchive.core.infra.sms.exception.ExternalApiException; +import site.timecapsulearchive.core.infra.sms.manager.SmsApiManager; /** * 유닛 테스트를 위한 의존성 클래스 집합체 */ public class UnitTestDependency { - public static S3PreSignedUrlManager s3PreSignedUrlManager() { - S3Config s3Config = new S3Config( - new S3Properties("a".repeat(32), "b".repeat(32), "origin", "temporary", "us-east")); - return new S3PreSignedUrlManager(s3Config); - } - public static GeoTransformManager geoTransformManager() { GeoTransformConfig geoTransformConfig = new GeoTransformConfig(); @@ -37,4 +35,20 @@ public static GeoTransformManager geoTransformManager() { public static HashEncryptionManager hashEncryptionManager() { return new HashEncryptionManager(new HashProperties("test")); } + + public static JwtProperties jwtProperties() { + return new JwtProperties("T".repeat(32), 3600, 3600, 3600); + } + + public static JwtFactory jwtFactory() { + return new JwtFactory(jwtProperties()); + } + + public static AESEncryptionManager aesEncryptionManager() { + return new AESEncryptionManager(new AESKeyProperties("A".repeat(32))); + } + + public static SmsApiManager smsApiManager() { + return (receiver, message) -> new SmsApiResponse(200, "success"); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java new file mode 100644 index 000000000..0bc76fe65 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java @@ -0,0 +1,18 @@ +package site.timecapsulearchive.core.common.fixture.domain; + +import site.timecapsulearchive.core.domain.member.entity.MemberTemporary; +import site.timecapsulearchive.core.domain.member.entity.SocialType; + +public class MemberTemporaryFixture { + + public static MemberTemporary memberTemporary(Long memberId) { + return MemberTemporary.builder() + .authId(memberId + "test_auth_id") + .tag(memberId + "test_tag") + .email(memberId + "test_email@gmail.com") + .nickname(memberId + "test_nickname") + .profileUrl(memberId + "test_profile_url") + .socialType(SocialType.GOOGLE) + .build(); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/security/TokenFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/security/TokenFixture.java new file mode 100644 index 000000000..83243b39a --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/security/TokenFixture.java @@ -0,0 +1,100 @@ +package site.timecapsulearchive.core.common.fixture.security; + +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import site.timecapsulearchive.core.common.dependency.UnitTestDependency; +import site.timecapsulearchive.core.global.config.security.JwtProperties; +import site.timecapsulearchive.core.global.security.jwt.JwtFactory; +import site.timecapsulearchive.core.global.security.jwt.TokenType; + +public class TokenFixture { + + private static final String TOKEN_TYPE_CLAIM_NAME = "token_type"; + private static final String ISSUER = "https://archive-timecapsule.kro.kr"; + + private static final JwtFactory jwtFactory = UnitTestDependency.jwtFactory(); + private static final JwtProperties jwtProperties = UnitTestDependency.jwtProperties(); + private static final SecretKey secretKey = Keys.hmacShaKeyFor( + jwtProperties.secretKey().getBytes(StandardCharsets.UTF_8)); + + public static String accessToken(Long memberId) { + return jwtFactory.createAccessToken(memberId); + } + + public static String refreshToken(Long memberId) { + return jwtFactory.createRefreshToken(memberId); + } + + public static String temporaryAccessToken(Long memberId) { + return jwtFactory.createTemporaryAccessToken(memberId); + } + + public static String invalidToken() { + return "trash"; + } + + public static String expiredToken(Long memberId, TokenType tokenType) { + Date now = new Date(); + Date validity = new Date(now.getTime() - jwtProperties.accessTokenValidityMs()); + + JwtBuilder jwtBuilder = getDefaultJwtBuilder(memberId, validity, secretKey) + .claim("token_type", tokenType); + + return jwtBuilder.compact(); + } + + private static JwtBuilder getDefaultJwtBuilder(Long memberId, Date validity, + SecretKey secretKey) { + JwtBuilder jwtBuilder = Jwts.builder() + .setExpiration(validity) + .setIssuer(ISSUER) + .signWith(secretKey, SignatureAlgorithm.HS256); + + if (memberId != null) { + jwtBuilder.setSubject(String.valueOf(memberId)); + } + + return jwtBuilder; + } + + + public static String forgedSignature(Long memberId, TokenType tokenType) { + Date now = new Date(); + Date validity = new Date(now.getTime() - jwtProperties.accessTokenValidityMs()); + + SecretKey forgedKey = Keys.hmacShaKeyFor("F".repeat(32).getBytes( + StandardCharsets.UTF_8)); + JwtBuilder jwtBuilder = getDefaultJwtBuilder(memberId, validity, forgedKey) + .claim(TOKEN_TYPE_CLAIM_NAME, tokenType); + + return jwtBuilder.compact(); + } + + public static String invalidTokenType(Long memberId, String tokenType) { + Date now = new Date(); + Date validity = new Date(now.getTime() + jwtProperties.accessTokenValidityMs()); + + JwtBuilder jwtBuilder = getDefaultJwtBuilder(memberId, validity, secretKey); + + if (tokenType != null) { + jwtBuilder.claim(TOKEN_TYPE_CLAIM_NAME, tokenType); + } + + return jwtBuilder.compact(); + } + + public static String noSubject(TokenType tokenType) { + Date now = new Date(); + Date validity = new Date(now.getTime() + jwtProperties.accessTokenValidityMs()); + + JwtBuilder jwtBuilder = getDefaultJwtBuilder(null, validity, secretKey) + .claim(TOKEN_TYPE_CLAIM_NAME, tokenType); + + return jwtBuilder.compact(); + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationServiceTest.java new file mode 100644 index 000000000..3d8037bc1 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationServiceTest.java @@ -0,0 +1,128 @@ +package site.timecapsulearchive.core.domain.auth.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import site.timecapsulearchive.core.common.dependency.UnitTestDependency; +import site.timecapsulearchive.core.common.fixture.domain.MemberTemporaryFixture; +import site.timecapsulearchive.core.domain.auth.data.dto.VerificationMessageSendDto; +import site.timecapsulearchive.core.domain.auth.exception.CertificationNumberNotFoundException; +import site.timecapsulearchive.core.domain.auth.exception.CertificationNumberNotMatchException; +import site.timecapsulearchive.core.domain.auth.repository.MessageAuthenticationCacheRepository; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member.entity.MemberTemporary; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; +import site.timecapsulearchive.core.infra.sms.manager.SmsApiManager; + +class MessageVerificationServiceTest { + + private static final Long MEMBER_ID = 1L; + private static final String RECEIVER = "010-0000-0000"; + private static final String APP_HASH_KEY = "test_key"; + + private final MessageAuthenticationCacheRepository messageAuthenticationCacheRepository = mock( + MessageAuthenticationCacheRepository.class); + private final MemberRepository memberRepository = mock(MemberRepository.class); + private final MemberTemporaryRepository memberTemporaryRepository = mock( + MemberTemporaryRepository.class); + private final SmsApiManager smsApiManager = UnitTestDependency.smsApiManager(); + + private final MessageVerificationService messageVerificationService = new MessageVerificationService( + messageAuthenticationCacheRepository, + smsApiManager, + memberRepository, + memberTemporaryRepository, + UnitTestDependency.aesEncryptionManager(), + UnitTestDependency.hashEncryptionManager() + ); + + @Test + void 인증번호를_전송하면_성공한다() { + //given + //when + VerificationMessageSendDto verificationMessageSendDto = messageVerificationService.sendVerificationMessage( + MEMBER_ID, RECEIVER, APP_HASH_KEY); + + //then + assertThat(verificationMessageSendDto).isNotNull(); + } + + @Test + void 저장된_인증번호를_찾을_수_없는_경우_예외가_발생한다() { + //given + String certificationNumber = "1234"; + given(messageAuthenticationCacheRepository.findMessageAuthenticationCodeByMemberId( + anyLong(), any())) + .willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> messageVerificationService.validVerificationMessage( + MEMBER_ID, certificationNumber, RECEIVER)) + .isInstanceOf(CertificationNumberNotFoundException.class); + } + + @Test + void 일치하지_않은_인증번호로_인증을_시도하면_예외가_발생한다() { + //given + String certificationNumber = "1234"; + given(messageAuthenticationCacheRepository.findMessageAuthenticationCodeByMemberId( + anyLong(), any())) + .willReturn(Optional.of("3456")); + + //when + //then + assertThatThrownBy(() -> messageVerificationService.validVerificationMessage( + MEMBER_ID, certificationNumber, RECEIVER)) + .isInstanceOf(CertificationNumberNotMatchException.class); + } + + @Test + void 인증번호가_일치하면_사용자를_저장한다() { + //given + String certificationNumber = "1234"; + given(messageAuthenticationCacheRepository.findMessageAuthenticationCodeByMemberId( + anyLong(), any())) + .willReturn(Optional.of(certificationNumber)); + given(memberTemporaryRepository.findById(anyLong())) + .willReturn(Optional.of(MemberTemporaryFixture.memberTemporary(MEMBER_ID))); + given(memberRepository.checkTagDuplication(any())).willReturn(false); + + //when + messageVerificationService.validVerificationMessage(MEMBER_ID, certificationNumber, + RECEIVER); + + //then + verify(memberRepository, times(1)).save(any(Member.class)); + } + + @Test + void 태그가_중복되면_태그를_교체한다() { + //given + String certificationNumber = "1234"; + MemberTemporary memberTemporary = MemberTemporaryFixture.memberTemporary(MEMBER_ID); + String originTag = memberTemporary.getTag(); + given(messageAuthenticationCacheRepository.findMessageAuthenticationCodeByMemberId( + anyLong(), any())) + .willReturn(Optional.of(certificationNumber)); + given(memberTemporaryRepository.findById(anyLong())) + .willReturn(Optional.of(memberTemporary)); + given(memberRepository.checkTagDuplication(any())).willReturn(true); + + //when + messageVerificationService.validVerificationMessage(MEMBER_ID, certificationNumber, + RECEIVER); + + //then + assertThat(memberTemporary.getTag()).isNotEqualTo(originTag); + } +} \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java new file mode 100644 index 000000000..4d07cd3b5 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java @@ -0,0 +1,100 @@ +package site.timecapsulearchive.core.domain.auth.service; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import site.timecapsulearchive.core.common.dependency.UnitTestDependency; +import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; +import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; +import site.timecapsulearchive.core.domain.auth.exception.AlreadyReIssuedTokenException; +import site.timecapsulearchive.core.domain.auth.repository.RefreshTokenCacheRepository; +import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; +import site.timecapsulearchive.core.global.security.jwt.JwtFactory; + +class TokenManagerTest { + + private static final Long MEMBER_ID = 1L; + + private final JwtFactory jwtFactory = UnitTestDependency.jwtFactory(); + private final RefreshTokenCacheRepository refreshTokenCacheRepository = mock( + RefreshTokenCacheRepository.class); + private final TokenManager tokenManager = new TokenManager(jwtFactory, refreshTokenCacheRepository); + + @Test + void 사용자_아이디로_토큰을_발급하면_액세스_토큰과_리프레시_토큰이_나온다() { + //given + //when + TokenDto newToken = tokenManager.createNewToken(MEMBER_ID); + + //then + assertSoftly(softly -> { + softly.assertThat(newToken.accessToken()).isNotBlank(); + softly.assertThat(newToken.refreshToken()).isNotBlank(); + softly.assertThat(newToken.expiresIn()).isGreaterThan(0); + softly.assertThat(newToken.refreshTokenExpiresIn()).isGreaterThan(0); + }); + } + + @Test + void 사용자_아이디로_임시인증_토큰을_발급하면_임시_액세스_토큰이_나온다() { + //given + //when + TemporaryTokenDto newToken = tokenManager.createTemporaryToken(MEMBER_ID); + + //then + assertSoftly(softly -> { + softly.assertThat(newToken.temporaryAccessToken()).isNotBlank(); + softly.assertThat(newToken.expiresIn()).isGreaterThan(0); + }); + } + + @Test + void 리프레시_토큰으로_재발급하면_액세스_토큰과_리프레시_토큰이_나온다() { + //given + String refreshToken = jwtFactory.createRefreshToken(MEMBER_ID); + given(refreshTokenCacheRepository.findRefreshTokenByMemberId(anyLong())) + .willReturn(Optional.of(refreshToken)); + + //when + TokenDto refreshedToken = tokenManager.reIssueToken(refreshToken); + + //then + assertSoftly(softly -> { + softly.assertThat(refreshedToken.accessToken()).isNotBlank(); + softly.assertThat(refreshedToken.refreshToken()).isNotBlank(); + softly.assertThat(refreshedToken.expiresIn()).isGreaterThan(0); + softly.assertThat(refreshedToken.refreshTokenExpiresIn()).isGreaterThan(0); + }); + } + + @Test + void 저장되지_않은_리프레시_토큰으로_재발급하면_예외가_발생한다() { + //given + String refreshToken = jwtFactory.createRefreshToken(MEMBER_ID); + given(refreshTokenCacheRepository.findRefreshTokenByMemberId(anyLong())) + .willReturn(Optional.empty()); + + //when + //then + assertThatThrownBy(() -> tokenManager.reIssueToken(refreshToken)) + .isInstanceOf(AlreadyReIssuedTokenException.class); + } + + @Test + void 재발급에_사용할_리프레시_토큰과_저장된_리프레시_토큰이_일치하지_않으면_예외가_발생한다() { + //given + String refreshToken = jwtFactory.createRefreshToken(MEMBER_ID); + given(refreshTokenCacheRepository.findRefreshTokenByMemberId(anyLong())) + .willReturn(Optional.of(jwtFactory.createRefreshToken(2L))); + + //when + //then + assertThatThrownBy(() -> tokenManager.reIssueToken(refreshToken)) + .isInstanceOf(InvalidTokenException.class); + } +} \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilterTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilterTest.java new file mode 100644 index 000000000..bf55ee6e2 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilterTest.java @@ -0,0 +1,146 @@ +package site.timecapsulearchive.core.global.security.jwt; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpHeaders; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RestController; +import site.timecapsulearchive.core.common.config.TestMockMvcSecurityConfig; +import site.timecapsulearchive.core.common.controller.AuthTestController; +import site.timecapsulearchive.core.common.fixture.security.TokenFixture; + +@WebMvcTest( + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = RestController.class), + } +) +@Import(value = TestMockMvcSecurityConfig.class) +@DisplayName("jwt_인증_필터_테스트") +class JwtAuthenticationFilterTest { + + private static final Long MEMBER_ID = 1L; + private static final String TOKEN_TYPE = "Bearer "; + private static final String NOT_EXIST_PASS_URL = "/pass"; + private static final String AUTHENTICATION_ERROR_CODE = "AUTH-003"; + + private MockMvc mockMvc; + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @BeforeEach + public void setUp() { + this.mockMvc = MockMvcBuilders.standaloneSetup(new AuthTestController()) + .apply(springSecurity(jwtAuthenticationFilter)) + .build(); + } + + @Test + void 올바른_액세스_토큰으로_인증하면_ok가_반환된다() throws Exception { + //given + String accessToken = TokenFixture.accessToken(MEMBER_ID); + + //when + ResultActions result = mockMvc + .perform( + get("/not-pass") + .header( + HttpHeaders.AUTHORIZATION, + TOKEN_TYPE + accessToken + ) + ); + + //then + result.andExpect(status().isOk()); + } + + @Test + void 변조된_액세스_토큰으로_인증하면_에러_코드가_반환된다() throws Exception { + //given + String accessToken = TokenFixture.forgedSignature(MEMBER_ID, TokenType.ACCESS); + + //when + ResultActions result = mockMvc + .perform( + get("/not-pass") + .header( + HttpHeaders.AUTHORIZATION, + TOKEN_TYPE + accessToken + ) + ); + + //then + result.andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value(AUTHENTICATION_ERROR_CODE)); + } + + @Test + void Bearer_prefix_없이_액세스_토큰만으로_인증하면_에러_코드가_반환된다() throws Exception { + //given + String accessToken = TokenFixture.accessToken(MEMBER_ID); + + //when + ResultActions result = mockMvc + .perform( + get("/not-pass") + .header( + HttpHeaders.AUTHORIZATION, + accessToken + ) + ); + + //then + result.andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value(AUTHENTICATION_ERROR_CODE)); + } + + @Test + void 유효하지_않은_액세스_토큰으로_인증하면_에러_코드가_반환된다() throws Exception { + //given + String invalidAccessToken = TokenFixture.invalidToken(); + + //when + ResultActions result = mockMvc.perform( + get("/not-pass") + .header(HttpHeaders.AUTHORIZATION, invalidAccessToken) + ); + + //then + result.andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value(AUTHENTICATION_ERROR_CODE)); + } + + @Test + void 액세스_토큰_없이_인증하면_에러코드가_반환된다() throws Exception { + //given + //when + ResultActions result = mockMvc.perform(get("/not-pass")); + + //then + result.andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value(AUTHENTICATION_ERROR_CODE)); + } + + @Test + void 인증이_통과되는_엔드포인트로_요청하면_ok가_반환된다() throws Exception { + //given + //when + ResultActions result = mockMvc.perform(get(NOT_EXIST_PASS_URL)); + + //then + result.andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java new file mode 100644 index 000000000..a80a6d32c --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java @@ -0,0 +1,122 @@ +package site.timecapsulearchive.core.global.security.jwt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import site.timecapsulearchive.core.common.dependency.UnitTestDependency; +import site.timecapsulearchive.core.common.fixture.security.TokenFixture; +import site.timecapsulearchive.core.domain.member.entity.Role; +import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; + +class JwtAuthenticationProviderTest { + + private static final Long MEMBER_ID = 1L; + + private final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider( + UnitTestDependency.jwtFactory()); + + @Test + void 액세스_토큰으로_인증하면_인증에_성공한다() { + //given + String accessToken = TokenFixture.accessToken(MEMBER_ID); + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + accessToken); + + //when + Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); + + //then + assertThat(authenticateResult.isAuthenticated()).isTrue(); + } + + @Test + void 액세스_토큰으로_인증하면_사용자_아이디를_확인할_수_있다() { + //given + String accessToken = TokenFixture.accessToken(MEMBER_ID); + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + accessToken); + + //when + Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); + + //then + assertThat((Long) authenticateResult.getPrincipal()).isEqualTo(MEMBER_ID); + } + + @Test + void 액세스_토큰으로_인증하면_사용자_권한을_확인할_수_있다() { + //given + String accessToken = TokenFixture.accessToken(MEMBER_ID); + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + accessToken); + + //when + Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); + + //then + assertThat(authenticateResult.getAuthorities()) + .isNotEmpty() + .allMatch(authority -> + authority.equals(new SimpleGrantedAuthority(Role.USER.getValue()))); + } + + @Test + void 임시인증_토큰으로_인증하면_인증에_성공한다() { + //given + String temporaryAccessToken = TokenFixture.temporaryAccessToken(MEMBER_ID); + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + temporaryAccessToken); + + //when + Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); + + //then + assertThat(authenticateResult.isAuthenticated()).isTrue(); + } + + @Test + void 임시인증_토큰으로_인증하면_사용자_아이디를_확인할_수_있다() { + //given + String temporaryAccessToken = TokenFixture.temporaryAccessToken(MEMBER_ID); + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + temporaryAccessToken); + + //when + Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); + + //then + assertThat((Long) authenticateResult.getPrincipal()).isEqualTo(MEMBER_ID); + } + + @Test + void 임시인증_토큰으로_인증하면_사용자_권한을_확인할_수_있다() { + //given + String temporaryAccessToken = TokenFixture.temporaryAccessToken(MEMBER_ID); + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + temporaryAccessToken); + + //when + Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); + + //then + assertThat(authenticateResult.getAuthorities()) + .isNotEmpty() + .allMatch(authority -> + authority.equals(new SimpleGrantedAuthority(Role.TEMPORARY.getValue()))); + } + + @Test + void 유효하지_않은_토큰으로_인증하면_예외가_발생한다() { + //given + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + TokenFixture.invalidToken()); + + //when + //then + assertThatThrownBy(() -> jwtAuthenticationProvider.authenticate(unauthenticated)) + .isInstanceOf(InvalidTokenException.class); + } +} \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java new file mode 100644 index 000000000..a28998508 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java @@ -0,0 +1,188 @@ +package site.timecapsulearchive.core.global.security.jwt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.Test; +import site.timecapsulearchive.core.common.dependency.UnitTestDependency; +import site.timecapsulearchive.core.common.fixture.security.TokenFixture; +import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; + +class JwtFactoryTest { + + private static final Long MEMBER_ID = 1L; + + private JwtFactory jwtFactory = UnitTestDependency.jwtFactory(); + + @Test + void 사용자_아이디로_액세스_토큰을_생성하면_액세스_토큰이_나온다() { + //given + //when + String accessToken = jwtFactory.createAccessToken(MEMBER_ID); + + //then + assertThat(accessToken).isNotBlank(); + } + + @Test + void 사용자_아이디로_리프레시_토큰을_생성하면_리프레시_토큰이_나온다() { + //given + //when + String refreshToken = jwtFactory.createRefreshToken(MEMBER_ID); + + //then + assertThat(refreshToken).isNotBlank(); + } + + @Test + void 사용자_아이디로_임시_인증_토큰을_생성하면_임시_인증_토큰이_나온다() { + //given + //when + String temporaryAccessToken = jwtFactory.createTemporaryAccessToken(MEMBER_ID); + + //then + assertThat(temporaryAccessToken).isNotBlank(); + } + + @Test + void 사용자_아이디로_액세스_토큰을_생성후_파싱하면_사용자_아이디가_나온다() { + //given + String accessToken = jwtFactory.createAccessToken(MEMBER_ID); + + //when + TokenParseResult result = jwtFactory.parse(accessToken, List.of(TokenType.ACCESS)); + + //then + assertThat(Long.valueOf(result.subject())).isEqualTo(MEMBER_ID); + } + + @Test + void 사용자_아이디로_액세스_토큰을_생성후_파싱하면_액세스_토큰_타입이_나온다() { + //given + String accessToken = jwtFactory.createAccessToken(MEMBER_ID); + + //when + TokenParseResult result = jwtFactory.parse(accessToken, List.of(TokenType.ACCESS)); + + //then + assertThat(result.tokenType()).isEqualTo(TokenType.ACCESS); + } + + @Test + void 사용자_아이디로_리프레시_토큰을_생성후_파싱하면_사용자_아이디가_나온다() { + //given + String accessToken = jwtFactory.createRefreshToken(MEMBER_ID); + + //when + TokenParseResult result = jwtFactory.parse(accessToken, List.of(TokenType.REFRESH)); + + //then + assertThat(Long.valueOf(result.subject())).isEqualTo(MEMBER_ID); + } + + @Test + void 사용자_아이디로_리프레시_토큰을_생성후_파싱하면_액세스_토큰_타입이_나온다() { + //given + String accessToken = jwtFactory.createRefreshToken(MEMBER_ID); + + //when + TokenParseResult result = jwtFactory.parse(accessToken, List.of(TokenType.REFRESH)); + + //then + assertThat(result.tokenType()).isEqualTo(TokenType.REFRESH); + } + + @Test + void 사용자_아이디로_임시인증_토큰을_생성후_파싱하면_사용자_아이디가_나온다() { + //given + String accessToken = jwtFactory.createTemporaryAccessToken(MEMBER_ID); + + //when + TokenParseResult result = jwtFactory.parse(accessToken, List.of(TokenType.TEMPORARY)); + + //then + assertThat(Long.valueOf(result.subject())).isEqualTo(MEMBER_ID); + } + + @Test + void 사용자_아이디로_임시인증_토큰을_생성후_파싱하면_액세스_토큰_타입이_나온다() { + //given + String accessToken = jwtFactory.createTemporaryAccessToken(MEMBER_ID); + + //when + TokenParseResult result = jwtFactory.parse(accessToken, List.of(TokenType.TEMPORARY)); + + //then + assertThat(result.tokenType()).isEqualTo(TokenType.TEMPORARY); + } + + @Test + void 토큰_타입이_공백인_토큰을_파싱하면_예외가_발생한다() { + //given + String token = TokenFixture.invalidTokenType(MEMBER_ID, ""); + List tokenTypes = List.of(TokenType.ACCESS, TokenType.REFRESH); + + //when + //then + assertThatThrownBy(() -> jwtFactory.parse(token, tokenTypes)) + .isInstanceOf(InvalidTokenException.class); + } + + @Test + void 토큰_타입이_없는_토큰을_파싱하면_예외가_발생한다() { + //given + String token = TokenFixture.invalidTokenType(MEMBER_ID, null); + List tokenTypes = List.of(TokenType.ACCESS, TokenType.REFRESH); + + //when + //then + assertThatThrownBy(() -> jwtFactory.parse(token, tokenTypes)) + .isInstanceOf(InvalidTokenException.class); + } + + @Test + void 추출할_토큰_타입이_다른_토큰을_파싱하면_예외가_발생한다() { + //given + String token = jwtFactory.createAccessToken(MEMBER_ID); + List tokenTypes = List.of(TokenType.REFRESH); + + //when + //then + assertThatThrownBy(() -> jwtFactory.parse(token, tokenTypes)) + .isInstanceOf(InvalidTokenException.class); + } + + @Test + void 서명이_다른_jwt_생성기로_생성한_토큰을_검증하면_예외가_발생한다() { + //given + String accessToken = TokenFixture.forgedSignature(MEMBER_ID, TokenType.ACCESS); + + //when + //then + assertThatThrownBy(() -> jwtFactory.validate(accessToken)) + .isInstanceOf(InvalidTokenException.class); + } + + @Test + void 시간이_지난_토큰으로_검증하면_예외가_발생한다() { + //given + String expiredToken = TokenFixture.expiredToken(MEMBER_ID, TokenType.ACCESS); + + //when + //then + assertThatThrownBy(() -> jwtFactory.validate(expiredToken)) + .isInstanceOf(InvalidTokenException.class); + } + + @Test + void 유효하지_않은_형식의_토큰으로_검증하면_예외가_발생한다() { + //given + String invalidToken = TokenFixture.invalidToken(); + + //when + //then + assertThatThrownBy(() -> jwtFactory.validate(invalidToken)) + .isInstanceOf(InvalidTokenException.class); + } +} \ No newline at end of file From 914b95bb78ed53907a8ec3b99e2e55adb8c01ef9 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 11:06:14 +0900 Subject: [PATCH 018/157] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/member/api/MemberApi.java | 24 +++++++++++++++++++ .../member/api/MemberApiController.java | 18 ++++++++++++++ .../member/data/dto/UpdateMemberDataDto.java | 8 +++++++ .../reqeust/MemberDetailUpdateRequest.java | 16 ------------- .../data/reqeust/UpdateMemberDataRequest.java | 20 ++++++++++++++++ .../member/repository/MemberRepository.java | 8 +++++++ .../domain/member/service/MemberService.java | 14 +++++++++++ 7 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/UpdateMemberDataDto.java delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/MemberDetailUpdateRequest.java create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java index 584ae1c73..593068254 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java @@ -11,6 +11,7 @@ import site.timecapsulearchive.core.domain.member.data.reqeust.CheckEmailDuplicationRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.CheckStatusRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateFCMTokenRequest; +import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateMemberDataRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateNotificationEnabledRequest; import site.timecapsulearchive.core.domain.member.data.response.CheckEmailDuplicationResponse; import site.timecapsulearchive.core.domain.member.data.response.MemberDetailResponse; @@ -160,4 +161,27 @@ ResponseEntity> getMemberNotifications( }) ResponseEntity> checkEmailDuplication( CheckEmailDuplicationRequest request); + + @Operation( + summary = "사용자 정보 수정", + description = "사용자 정보(닉네임, 태그)를 수정한다.", + security = {@SecurityRequirement(name = "user_token")}, + tags = {"member"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리 완료" + ), + @ApiResponse( + responseCode = "404", + description = "해당 멤버가 존재하지 않을 때 발생하는 예외", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + }) + ResponseEntity> updateMemberData( + Long memberId, + UpdateMemberDataRequest request + ); + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java index 8b907a2e2..4ac753c80 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java @@ -16,6 +16,7 @@ import site.timecapsulearchive.core.domain.member.data.reqeust.CheckEmailDuplicationRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.CheckStatusRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateFCMTokenRequest; +import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateMemberDataRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateNotificationEnabledRequest; import site.timecapsulearchive.core.domain.member.data.response.CheckEmailDuplicationResponse; import site.timecapsulearchive.core.domain.member.data.response.MemberDetailResponse; @@ -131,4 +132,21 @@ public ResponseEntity> checkEmailDuplicat ) ); } + + @Override + @PatchMapping("/data") + public ResponseEntity> updateMemberData( + @AuthenticationPrincipal Long memberId, + @Valid @RequestBody UpdateMemberDataRequest request + ) { + memberService.updateMemberData(memberId, request.toDto()); + + return ResponseEntity.ok( + ApiSpec.empty( + SuccessCode.SUCCESS + ) + ); + } + + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/UpdateMemberDataDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/UpdateMemberDataDto.java new file mode 100644 index 000000000..028453075 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/UpdateMemberDataDto.java @@ -0,0 +1,8 @@ +package site.timecapsulearchive.core.domain.member.data.dto; + +public record UpdateMemberDataDto( + String nickname, + String tag +) { + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/MemberDetailUpdateRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/MemberDetailUpdateRequest.java deleted file mode 100644 index 8611f24ef..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/MemberDetailUpdateRequest.java +++ /dev/null @@ -1,16 +0,0 @@ -package site.timecapsulearchive.core.domain.member.data.reqeust; - -import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.web.multipart.MultipartFile; - -@Schema(description = "회원 업데이트 포맷") -public record MemberDetailUpdateRequest( - - @Schema(description = "닉네임") - String nickname, - - @Schema(description = "프로플 이미지") - MultipartFile profileImage -) { - -} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java new file mode 100644 index 000000000..3ab7571be --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java @@ -0,0 +1,20 @@ +package site.timecapsulearchive.core.domain.member.data.reqeust; + +import io.swagger.v3.oas.annotations.media.Schema; +import site.timecapsulearchive.core.domain.member.data.dto.UpdateMemberDataDto; + +@Schema(description = "회원 업데이트 포맷") +public record UpdateMemberDataRequest( + + @Schema(description = "닉네임") + String nickname, + + @Schema(description = "태그") + String tag +) { + + public UpdateMemberDataDto toDto() { + return new UpdateMemberDataDto(nickname, tag); + } + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java index 02c68c260..deb856cb6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberRepository.java @@ -26,4 +26,12 @@ int updateMemberNotificationEnabled( @Param("memberId") Long memberId, @Param("notificationEnabled") Boolean notificationEnabled ); + + @Modifying(clearAutomatically = true) + @Query("UPDATE Member m SET m.nickname = :nickname, m.tag = :tag WHERE m.id = :memberId") + int updateMemberData( + @Param("memberId") Long memberId, + @Param("nickname") String nickname, + @Param("tag") String tag + ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 2bf412d28..0de9150f2 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -11,6 +11,7 @@ import site.timecapsulearchive.core.domain.member.data.dto.MemberDetailDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.dto.SignUpRequestDto; +import site.timecapsulearchive.core.domain.member.data.dto.UpdateMemberDataDto; import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; import site.timecapsulearchive.core.domain.member.data.mapper.MemberMapper; import site.timecapsulearchive.core.domain.member.data.response.CheckEmailDuplicationResponse; @@ -223,4 +224,17 @@ public Member findMemberById(final Long memberId) { return memberRepository.findMemberById(memberId) .orElseThrow(MemberNotFoundException::new); } + + @Transactional + public void updateMemberData( + final Long memberId, + final UpdateMemberDataDto updateMemberDataDto + ) { + final int updateMemberData = memberRepository.updateMemberData(memberId, + updateMemberDataDto.nickname(), updateMemberDataDto.tag()); + + if (updateMemberData != 1) { + throw new MemberNotFoundException(); + } + } } From 9745f971d256b635d66044f8f5ab42a66ab67206 Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 23 Jun 2024 11:09:25 +0900 Subject: [PATCH 019/157] =?UTF-8?q?feat=20:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20API,=20=EB=B8=94=EB=9E=99=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그아웃 API 추가 - 블랙리스트 추가(filter) --- .../core/domain/auth/api/AuthApi.java | 36 +++++++++++++++++++ .../domain/auth/api/AuthApiController.java | 18 ++++++++-- .../repository/BlackListCacheRepository.java | 27 ++++++++++++++ .../RefreshTokenCacheRepository.java | 5 +++ .../core/domain/auth/service/AuthManager.java | 6 ++++ .../domain/auth/service/TokenManager.java | 12 +++++++ .../jwt/JwtAuthenticationProvider.java | 15 +++++++- .../security/jwt/JwtAuthenticationToken.java | 5 +-- .../core/global/security/jwt/JwtFactory.java | 20 ++++++++++- 9 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/BlackListCacheRepository.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java index b059bb8d8..806e2d4f7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import site.timecapsulearchive.core.domain.auth.data.request.EmailSignInRequest; import site.timecapsulearchive.core.domain.auth.data.request.EmailSignUpRequest; import site.timecapsulearchive.core.domain.auth.data.request.SignInRequest; @@ -133,6 +134,41 @@ public interface AuthApi { }) ResponseEntity> signInWithSocialProvider(SignInRequest request); + @Operation( + summary = "다른 소셜 프로바이더의 앱으로 인증한 클라이언트 아이디 로그아웃", + description = """ + 다른 소셜 프로바이더의 앱으로 인증한 클라이언트의 아이디로 로그아웃한다. + + 로그인된 사용자만 가능하다. + """, + tags = {"auth"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "ok" + ), + @ApiResponse( + responseCode = "400", + description = """ + 요청이 잘못되어 발생하는 오류이다. +
    +
  • 올바르지 않은 요청인 경우 예외가 발생한다.
  • +
  • 인증되지 않은 사용자인 경우 예외가 발생한다.
  • +
+ """, + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "로그아웃을 요청한 멤버를 찾을 수 없는 경우 예외가 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + }) + ResponseEntity> signOutWithSocialProvider( + Authentication authentication + ); + @Operation( summary = "임시 인증 토큰 재발급", description = "인증되지 않은 사용자가 인증할 수 있는 임시 인증 토큰을 재발급한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index c23d94554..aca37f91a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -4,6 +4,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -26,9 +27,6 @@ import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.VerificationMessageSendResponse; import site.timecapsulearchive.core.domain.auth.service.AuthManager; -import site.timecapsulearchive.core.domain.auth.service.MessageVerificationService; -import site.timecapsulearchive.core.domain.auth.service.TokenManager; -import site.timecapsulearchive.core.domain.member.service.MemberService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -143,6 +141,20 @@ public ResponseEntity> signInWithSocialProvider( ); } + @PostMapping( + value = "/sign-out", + produces = {"application/json"} + ) + @Override + public ResponseEntity> signOutWithSocialProvider( + Authentication authentication + ) { + authManager.signOut((Long) authentication.getPrincipal(), + (String) authentication.getCredentials()); + + return ResponseEntity.ok(ApiSpec.empty(SuccessCode.SUCCESS)); + } + @PostMapping( value = "/verification/send-message", consumes = {"application/json"}, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/BlackListCacheRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/BlackListCacheRepository.java new file mode 100644 index 000000000..82a70f1ba --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/BlackListCacheRepository.java @@ -0,0 +1,27 @@ +package site.timecapsulearchive.core.domain.auth.repository; + +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class BlackListCacheRepository { + + private static final String PREFIX = "blackList_memberId_accessToken:"; + + private final StringRedisTemplate redisTemplate; + + public void save(final Long memberId, final String accessToken, long leftTime) { + redisTemplate.opsForValue() + .set(PREFIX + memberId, accessToken, leftTime, TimeUnit.MILLISECONDS); + } + + public boolean exist(Long memberId) { + String accessToken = redisTemplate.opsForValue() + .get(PREFIX + memberId); + + return accessToken != null; + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/RefreshTokenCacheRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/RefreshTokenCacheRepository.java index 517f278e9..2e0d17929 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/RefreshTokenCacheRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/repository/RefreshTokenCacheRepository.java @@ -30,4 +30,9 @@ public void save( public Optional findRefreshTokenByMemberId(final Long memberId) { return Optional.ofNullable(redisTemplate.opsForValue().get(PREFIX + memberId)); } + + public void remove(Long memberId) { + redisTemplate.opsForValue() + .getAndDelete(PREFIX + memberId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index da72345b9..b38551fa4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -95,4 +95,10 @@ public TokenDto signInWithEmail(final String email, final String password) { return tokenManager.createNewToken(verifiedEmailMemberId); } + + public void signOut(Long memberId, String accessToken) { + tokenManager.removeRefreshToken(memberId); + + tokenManager.addBlackList(memberId, accessToken); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java index 1af838fc9..6b786e984 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/TokenManager.java @@ -6,6 +6,7 @@ import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; import site.timecapsulearchive.core.domain.auth.exception.AlreadyReIssuedTokenException; +import site.timecapsulearchive.core.domain.auth.repository.BlackListCacheRepository; import site.timecapsulearchive.core.domain.auth.repository.RefreshTokenCacheRepository; import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; import site.timecapsulearchive.core.global.security.jwt.JwtFactory; @@ -18,6 +19,7 @@ public class TokenManager { private final JwtFactory jwtFactory; private final RefreshTokenCacheRepository refreshTokenCacheRepository; + private final BlackListCacheRepository blackListCacheRepository; /** * 새로운 액세스 토큰과 리프레시 토큰을 발급한다. - 액세스 토큰(멤버 아이디) - 리프레시 토큰(데이터베이스에 저장된 사용자 식별자) @@ -80,4 +82,14 @@ public TokenDto reIssueToken(final String refreshToken) { return createToken(memberId, refreshToken); } + + public void removeRefreshToken(final Long memberId) { + refreshTokenCacheRepository.remove(memberId); + } + + public void addBlackList(final Long memberId, final String accessToken) { + long leftTime = jwtFactory.getLeftTime(accessToken); + + blackListCacheRepository.save(memberId, accessToken, leftTime); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProvider.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProvider.java index 94404cf6d..1d5948a8b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProvider.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProvider.java @@ -1,17 +1,24 @@ package site.timecapsulearchive.core.global.security.jwt; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; +import site.timecapsulearchive.core.domain.auth.repository.BlackListCacheRepository; +import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; @Component @RequiredArgsConstructor public class JwtAuthenticationProvider implements AuthenticationProvider { + private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationProvider.class); private final JwtFactory jwtFactory; + private final BlackListCacheRepository blackListCacheRepository; @Override public Authentication authenticate(final Authentication authentication) @@ -25,11 +32,17 @@ public Authentication authenticate(final Authentication authentication) ); final Long memberId = getMemberId(tokenParseResult); + final boolean isInBlackList = blackListCacheRepository.exist(memberId); + if (isInBlackList) { + log.error("블랙리스트 토큰으로 접근 시도 id: {}, accessToken: {}", memberId, accessToken); + throw new InvalidTokenException(); + } + if (tokenParseResult.tokenType() == TokenType.TEMPORARY) { return JwtAuthenticationToken.authenticatedWithTemporary(memberId); } - return JwtAuthenticationToken.authenticatedWithAccess(memberId); + return JwtAuthenticationToken.authenticatedWithAccess(memberId, accessToken); } private Long getMemberId(final TokenParseResult tokenParseResult) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationToken.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationToken.java index 0381063a0..5dfb26b48 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationToken.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationToken.java @@ -39,9 +39,10 @@ private static List getAuthorities(final Role role) { } public static JwtAuthenticationToken authenticatedWithAccess( - final Long memberId + final Long memberId, + final String accessToken ) { - return new JwtAuthenticationToken(null, memberId, getAuthorities(Role.USER), true); + return new JwtAuthenticationToken(accessToken, memberId, getAuthorities(Role.USER), true); } public static JwtAuthenticationToken unauthenticated(final String accessToken) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java index 1e1709913..c61af6001 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtFactory.java @@ -7,6 +7,8 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.List; import javax.crypto.SecretKey; @@ -54,7 +56,7 @@ public String createAccessToken(final Long memberId) { /** * 사용자 식별자를 받아서 리프레시 토큰 반환 * - * @param memberInfoKey 사용자 식별자 + * @param memberId 사용자 식별자 * @return 리프레시 토큰 */ public String createRefreshToken(final Long memberId) { @@ -156,4 +158,20 @@ public long getRefreshTokenExpiresIn() { public long getTemporaryTokenExpiresIn() { return temporaryValidityMs; } + + public long getLeftTime(final String accessToken) { + Instant expiration; + try { + expiration = jwtParser().parseClaimsJws(accessToken) + .getBody() + .getExpiration() + .toInstant(); + } catch (final JwtException | IllegalArgumentException e) { + throw new InvalidTokenException(e); + } + + Instant now = Instant.now(); + + return Duration.between(now, expiration).toMillis(); + } } From 078e792f786b12d39ce7cee83292be2f2a14ca3f Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 23 Jun 2024 11:09:38 +0900 Subject: [PATCH 020/157] =?UTF-8?q?test=20:=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/TestMockMvcSecurityConfig.java | 10 ++++++- .../common/dependency/UnitTestDependency.java | 2 +- .../domain/auth/service/TokenManagerTest.java | 10 ++++--- .../jwt/JwtAuthenticationProviderTest.java | 26 ++++++++++++++++++- .../global/security/jwt/JwtFactoryTest.java | 23 ++++++++++++++++ 5 files changed, 65 insertions(+), 6 deletions(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java index c5dcbe1e7..0030a2835 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java @@ -1,5 +1,8 @@ package site.timecapsulearchive.core.common.config; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; import com.fasterxml.jackson.databind.ObjectMapper; @@ -15,6 +18,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatchers; import site.timecapsulearchive.core.common.dependency.UnitTestDependency; +import site.timecapsulearchive.core.domain.auth.repository.BlackListCacheRepository; import site.timecapsulearchive.core.domain.member.entity.Role; import site.timecapsulearchive.core.global.api.limit.ApiLimitCheckInterceptor; import site.timecapsulearchive.core.global.api.limit.ApiLimitProperties; @@ -64,7 +68,11 @@ private RequestMatcher notRequireAuthenticationMatcher() { @Bean public JwtAuthenticationProvider jwtAuthenticationProvider() { - return new JwtAuthenticationProvider(UnitTestDependency.jwtFactory()); + BlackListCacheRepository blackListCacheRepository = mock(BlackListCacheRepository.class); + given(blackListCacheRepository.exist(anyLong())).willReturn(Boolean.FALSE); + + return new JwtAuthenticationProvider(UnitTestDependency.jwtFactory(), + blackListCacheRepository); } @Bean diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java index c4fca06c6..dc6986837 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/dependency/UnitTestDependency.java @@ -37,7 +37,7 @@ public static HashEncryptionManager hashEncryptionManager() { } public static JwtProperties jwtProperties() { - return new JwtProperties("T".repeat(32), 3600, 3600, 3600); + return new JwtProperties("T".repeat(32), 86400000L, 2592000000L, 3600000L); } public static JwtFactory jwtFactory() { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java index 4d07cd3b5..cb1d4cadb 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/auth/service/TokenManagerTest.java @@ -1,6 +1,6 @@ package site.timecapsulearchive.core.domain.auth.service; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; @@ -12,6 +12,7 @@ import site.timecapsulearchive.core.domain.auth.data.dto.TemporaryTokenDto; import site.timecapsulearchive.core.domain.auth.data.dto.TokenDto; import site.timecapsulearchive.core.domain.auth.exception.AlreadyReIssuedTokenException; +import site.timecapsulearchive.core.domain.auth.repository.BlackListCacheRepository; import site.timecapsulearchive.core.domain.auth.repository.RefreshTokenCacheRepository; import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; import site.timecapsulearchive.core.global.security.jwt.JwtFactory; @@ -23,7 +24,10 @@ class TokenManagerTest { private final JwtFactory jwtFactory = UnitTestDependency.jwtFactory(); private final RefreshTokenCacheRepository refreshTokenCacheRepository = mock( RefreshTokenCacheRepository.class); - private final TokenManager tokenManager = new TokenManager(jwtFactory, refreshTokenCacheRepository); + private final BlackListCacheRepository blackListCacheRepository = mock( + BlackListCacheRepository.class); + private final TokenManager tokenManager = new TokenManager(jwtFactory, + refreshTokenCacheRepository, blackListCacheRepository); @Test void 사용자_아이디로_토큰을_발급하면_액세스_토큰과_리프레시_토큰이_나온다() { @@ -64,7 +68,7 @@ class TokenManagerTest { TokenDto refreshedToken = tokenManager.reIssueToken(refreshToken); //then - assertSoftly(softly -> { + assertSoftly(softly -> { softly.assertThat(refreshedToken.accessToken()).isNotBlank(); softly.assertThat(refreshedToken.refreshToken()).isNotBlank(); softly.assertThat(refreshedToken.expiresIn()).isGreaterThan(0); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java index a80a6d32c..42df4cd83 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationProviderTest.java @@ -2,12 +2,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import org.junit.jupiter.api.Test; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import site.timecapsulearchive.core.common.dependency.UnitTestDependency; import site.timecapsulearchive.core.common.fixture.security.TokenFixture; +import site.timecapsulearchive.core.domain.auth.repository.BlackListCacheRepository; import site.timecapsulearchive.core.domain.member.entity.Role; import site.timecapsulearchive.core.global.error.exception.InvalidTokenException; @@ -15,8 +18,10 @@ class JwtAuthenticationProviderTest { private static final Long MEMBER_ID = 1L; + private final BlackListCacheRepository blackListCacheRepository = mock( + BlackListCacheRepository.class); private final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider( - UnitTestDependency.jwtFactory()); + UnitTestDependency.jwtFactory(), blackListCacheRepository); @Test void 액세스_토큰으로_인증하면_인증에_성공한다() { @@ -24,6 +29,7 @@ class JwtAuthenticationProviderTest { String accessToken = TokenFixture.accessToken(MEMBER_ID); JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( accessToken); + given(blackListCacheRepository.exist(MEMBER_ID)).willReturn(Boolean.FALSE); //when Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); @@ -38,6 +44,7 @@ class JwtAuthenticationProviderTest { String accessToken = TokenFixture.accessToken(MEMBER_ID); JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( accessToken); + given(blackListCacheRepository.exist(MEMBER_ID)).willReturn(Boolean.FALSE); //when Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); @@ -52,6 +59,7 @@ class JwtAuthenticationProviderTest { String accessToken = TokenFixture.accessToken(MEMBER_ID); JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( accessToken); + given(blackListCacheRepository.exist(MEMBER_ID)).willReturn(Boolean.FALSE); //when Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); @@ -69,6 +77,7 @@ class JwtAuthenticationProviderTest { String temporaryAccessToken = TokenFixture.temporaryAccessToken(MEMBER_ID); JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( temporaryAccessToken); + given(blackListCacheRepository.exist(MEMBER_ID)).willReturn(Boolean.FALSE); //when Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); @@ -83,6 +92,7 @@ class JwtAuthenticationProviderTest { String temporaryAccessToken = TokenFixture.temporaryAccessToken(MEMBER_ID); JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( temporaryAccessToken); + given(blackListCacheRepository.exist(MEMBER_ID)).willReturn(Boolean.FALSE); //when Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); @@ -97,6 +107,7 @@ class JwtAuthenticationProviderTest { String temporaryAccessToken = TokenFixture.temporaryAccessToken(MEMBER_ID); JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( temporaryAccessToken); + given(blackListCacheRepository.exist(MEMBER_ID)).willReturn(Boolean.FALSE); //when Authentication authenticateResult = jwtAuthenticationProvider.authenticate(unauthenticated); @@ -119,4 +130,17 @@ class JwtAuthenticationProviderTest { assertThatThrownBy(() -> jwtAuthenticationProvider.authenticate(unauthenticated)) .isInstanceOf(InvalidTokenException.class); } + + @Test + void 블랙리스트에_있는_토큰으로_접근하면_예외가_발생한다() { + //given + JwtAuthenticationToken unauthenticated = JwtAuthenticationToken.unauthenticated( + TokenFixture.accessToken(MEMBER_ID)); + given(blackListCacheRepository.exist(MEMBER_ID)).willReturn(Boolean.TRUE); + + //when + //then + assertThatThrownBy(() -> jwtAuthenticationProvider.authenticate(unauthenticated)) + .isInstanceOf(InvalidTokenException.class); + } } \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java index a28998508..e6221f13f 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/global/security/jwt/JwtFactoryTest.java @@ -185,4 +185,27 @@ class JwtFactoryTest { assertThatThrownBy(() -> jwtFactory.validate(invalidToken)) .isInstanceOf(InvalidTokenException.class); } + + @Test + void 액세스_토큰을_주면_현재_시간과의_차이_밀리초를_반환한다() { + //given + String accessToken = TokenFixture.accessToken(MEMBER_ID); + + //when + long leftTime = jwtFactory.getLeftTime(accessToken); + + //then + assertThat(leftTime).isPositive(); + } + + @Test + void 이미_지난_액세스_토큰을_주면_예외가_발생한다() { + //given + String expiredAccessToken = TokenFixture.expiredToken(MEMBER_ID, TokenType.ACCESS); + + //when + //then + assertThatThrownBy(() -> jwtFactory.getLeftTime(expiredAccessToken)) + .isInstanceOf(InvalidTokenException.class); + } } \ No newline at end of file From 73a7a8753f7f61424f1041cd326816229ecec913 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 11:24:35 +0900 Subject: [PATCH 021/157] =?UTF-8?q?fix=20:=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/auth/api/AuthApi.java | 59 -------- .../domain/auth/api/AuthApiController.java | 42 ------ .../auth/data/request/EmailSignInRequest.java | 20 --- .../auth/data/request/EmailSignUpRequest.java | 20 --- .../core/domain/member/api/MemberApi.java | 19 --- .../member/api/MemberApiController.java | 14 -- .../repository/MemberQueryRepository.java | 8 +- .../repository/MemberQueryRepositoryImpl.java | 31 ----- .../domain/member/service/MemberService.java | 52 ------- .../member/service/MemberServiceTest.java | 127 ------------------ 10 files changed, 2 insertions(+), 390 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignInRequest.java delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignUpRequest.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java index b059bb8d8..f730a565a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java @@ -8,8 +8,6 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.ResponseEntity; -import site.timecapsulearchive.core.domain.auth.data.request.EmailSignInRequest; -import site.timecapsulearchive.core.domain.auth.data.request.EmailSignUpRequest; import site.timecapsulearchive.core.domain.auth.data.request.SignInRequest; import site.timecapsulearchive.core.domain.auth.data.request.SignUpRequest; import site.timecapsulearchive.core.domain.auth.data.request.TemporaryTokenReIssueRequest; @@ -255,62 +253,5 @@ ResponseEntity> validVerificationMessage( Long memberId, VerificationNumberValidRequest request ); - - @Operation( - summary = "이메일로 회원가입", - description = """ - 이메일로 회원가입 한다. - - 인증되지 않은 상태이므로 전화 번호 인증을 해야한다. - """, - tags = {"auth"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity> signUpWithEmail(EmailSignUpRequest request); - - @Operation( - summary = "이메일로 로그인", - description = """ - 이메일로 로그인 한다. - - 완전히 인증된 상태의 유저만 가능하다. - """, - tags = {"auth"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ), - @ApiResponse( - responseCode = "400", - description = """ - 요청이 잘못되어 발생하는 오류이다. -
    -
  • 올바르지 않은 요청인 경우 예외가 발생한다.
  • -
  • 인증되지 않은 사용자인 경우 예외가 발생한다.
  • -
- """, - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - @ApiResponse( - responseCode = "401", - description = """ - 이메일 또는 비밀번호가 올바르지 않은 경우 발생하는 오류이다. (일치하지 않는 경우도 포함) - """, - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ), - @ApiResponse( - responseCode = "404", - description = "로그인을 요청한 멤버를 찾을 수 없는 경우 예외가 발생한다.", - content = @Content(schema = @Schema(implementation = ErrorResponse.class)) - ) - }) - ResponseEntity> signInWithEmail(EmailSignInRequest request); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index ab37c0ac9..cea44738d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -10,8 +10,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import site.timecapsulearchive.core.domain.auth.data.request.EmailSignInRequest; -import site.timecapsulearchive.core.domain.auth.data.request.EmailSignUpRequest; import site.timecapsulearchive.core.domain.auth.data.request.SignInRequest; import site.timecapsulearchive.core.domain.auth.data.request.SignUpRequest; import site.timecapsulearchive.core.domain.auth.data.request.TemporaryTokenReIssueRequest; @@ -201,44 +199,4 @@ public ResponseEntity> validVerificationMessage( ) ); } - - @PostMapping( - value = "/sign-up/email", - produces = {"application/json"}, - consumes = {"application/json"} - ) - @Override - public ResponseEntity> signUpWithEmail( - @Valid @RequestBody final EmailSignUpRequest request - ) { - final Long id = memberService.createMemberWithEmailAndPassword(request.email(), - request.password()); - - return ResponseEntity.ok( - ApiSpec.success( - SuccessCode.SUCCESS, - tokenService.createTemporaryToken(id) - ) - ); - } - - @PostMapping( - value = "/sign-in/email", - produces = {"application/json"}, - consumes = {"application/json"} - ) - @Override - public ResponseEntity> signInWithEmail( - @Valid @RequestBody final EmailSignInRequest request - ) { - final Long id = memberService.findVerifiedMemberIdByEmailAndPassword(request.email(), - request.password()); - - return ResponseEntity.ok( - ApiSpec.success( - SuccessCode.SUCCESS, - tokenService.createNewToken(id) - ) - ); - } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignInRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignInRequest.java deleted file mode 100644 index 2566946f7..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignInRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package site.timecapsulearchive.core.domain.auth.data.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; - -@Schema(description = "이메일 로그인 요청") -public record EmailSignInRequest( - - @Schema(description = "이메일") - @NotBlank - @Email - String email, - - @Schema(description = "비밀번호") - @NotBlank - String password -) { - -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignUpRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignUpRequest.java deleted file mode 100644 index 9d220b3ba..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/data/request/EmailSignUpRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package site.timecapsulearchive.core.domain.auth.data.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; - -@Schema(description = "이메일 회원가입 요청") -public record EmailSignUpRequest( - - @Schema(description = "사용할 이메일") - @NotBlank - @Email - String email, - - @Schema(description = "사용할 비밀번호") - @NotBlank - String password -) { - -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java index 593068254..5d53b4527 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java @@ -143,25 +143,6 @@ ResponseEntity> getMemberNotifications( ZonedDateTime createdAt ); - @Operation( - summary = "이메일 중복 조회", - description = "이메일의 중복을 조회한다.", - security = {@SecurityRequirement(name = "user_token")}, - tags = {"member"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "처리 완료" - ), - @ApiResponse( - responseCode = "400", - description = "입력 이메일이 없거나 형식이 안맞는 경우 발생하는 예외" - ) - }) - ResponseEntity> checkEmailDuplication( - CheckEmailDuplicationRequest request); - @Operation( summary = "사용자 정보 수정", description = "사용자 정보(닉네임, 태그)를 수정한다.", diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java index 4ac753c80..aef134fff 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java @@ -120,19 +120,6 @@ public ResponseEntity> getMemberNotific ); } - @PostMapping("/check-duplication/email") - @Override - public ResponseEntity> checkEmailDuplication( - @Valid @RequestBody CheckEmailDuplicationRequest request - ) { - return ResponseEntity.ok( - ApiSpec.success( - SuccessCode.SUCCESS, - memberService.checkEmailDuplication(request.email()) - ) - ); - } - @Override @PatchMapping("/data") public ResponseEntity> updateMemberData( @@ -148,5 +135,4 @@ public ResponseEntity> updateMemberData( ); } - } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java index 13827ea1f..8d6904371 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java @@ -37,13 +37,9 @@ List findMemberNotificationDtos( final ZonedDateTime createdAt ); - Optional findEmailVerifiedCheckDtoByEmail(final String email); - - Boolean checkEmailDuplication(final String email); - Optional findIsAlarmByMemberId(final Long memberId); - List findMemberIdsByIds(List ids); + List findMemberIdsByIds(final List ids); - boolean checkTagDuplication(String tag); + boolean checkTagDuplication(final String tag); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java index 0afced1fb..7f060ab40 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java @@ -131,37 +131,6 @@ public List findMemberNotificationDtos( .fetch(); } - @Override - public Optional findEmailVerifiedCheckDtoByEmail( - final String email - ) { - return Optional.ofNullable( - query - .select( - Projections.constructor( - EmailVerifiedCheckDto.class, - member.id, - member.isVerified, - member.email, - member.password - ) - ) - .from(member) - .where(member.email.eq(email)) - .fetchOne() - ); - } - - @Override - public Boolean checkEmailDuplication(final String email) { - final Integer count = query.selectOne() - .from(member) - .where(member.email.eq(email)) - .fetchFirst(); - - return count != null; - } - @Override public Optional findIsAlarmByMemberId(final Long memberId) { return Optional.ofNullable( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 0de9150f2..1aa278b29 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -37,7 +37,6 @@ public class MemberService { private final MemberRepository memberRepository; private final MemberTemporaryRepository memberTemporaryRepository; - private final PasswordEncoder passwordEncoder; private final MemberMapper memberMapper; @@ -162,57 +161,6 @@ public MemberNotificationSliceResponse findNotificationSliceByMemberId( ); } - @Transactional - public Long createMemberWithEmailAndPassword(final String email, final String password) { - final String encodedPassword = passwordEncoder.encode(password); - final Member member = memberMapper.createMemberWithEmail(email, encodedPassword); - - boolean isDuplicateTag = memberRepository.checkTagDuplication(member.getTag()); - if (isDuplicateTag) { - log.warn("member tag duplicate - email:{}, tag:{}", member.getEmail(), - member.getTag()); - member.updateTagLowerCaseSocialType(); - log.warn("member tag update - tag: {}", member.getTag()); - } - - final Member savedMember = memberRepository.save(member); - - return savedMember.getId(); - } - - public Long findVerifiedMemberIdByEmailAndPassword(final String email, final String password) { - final EmailVerifiedCheckDto dto = memberRepository.findEmailVerifiedCheckDtoByEmail( - email) - .orElseThrow(MemberNotFoundException::new); - - if (!dto.isVerified()) { - throw new NotVerifiedMemberException(); - } - - if (isNotMatched(email, password, dto.email(), dto.password())) { - throw new CredentialsNotMatchedException(); - } - - return dto.memberId(); - } - - private boolean isNotMatched( - final String inputEmail, - final String inputPassword, - final String email, - final String password - ) { - return !inputEmail.equals(email) || - password == null || - !passwordEncoder.matches(inputPassword, password); - } - - public CheckEmailDuplicationResponse checkEmailDuplication(final String email) { - Boolean isDuplicated = memberRepository.checkEmailDuplication(email); - - return new CheckEmailDuplicationResponse(isDuplicated); - } - public MemberNotificationStatusResponse checkNotificationStatus(final Long memberId) { final Boolean isAlarm = memberRepository.findIsAlarmByMemberId(memberId) .orElseThrow(MemberNotFoundException::new); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java index dd2095c84..49a51b33f 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java @@ -1,20 +1,8 @@ package site.timecapsulearchive.core.domain.member.service; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import java.util.Optional; -import org.junit.jupiter.api.Test; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import site.timecapsulearchive.core.domain.member.data.dto.EmailVerifiedCheckDto; import site.timecapsulearchive.core.domain.member.data.mapper.MemberMapper; -import site.timecapsulearchive.core.domain.member.exception.CredentialsNotMatchedException; -import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; -import site.timecapsulearchive.core.domain.member.exception.NotVerifiedMemberException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; @@ -23,126 +11,11 @@ class MemberServiceTest { private final MemberRepository memberRepository = mock(MemberRepository.class); private final MemberTemporaryRepository memberTemporaryRepository = mock( MemberTemporaryRepository.class); - private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); private final MemberMapper memberMapper = mock(MemberMapper.class); private final MemberService memberService = new MemberService( memberRepository, memberTemporaryRepository, - passwordEncoder, memberMapper ); - - @Test - void 이메일과_패스워드로_존재하지_않는_회원_아이디_검색하면_예외가_발생한다() { - //given - String email = "test@google.com"; - String password = "test-password"; - given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) - .willReturn(Optional.empty()); - - //when, then - assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password)) - .isExactlyInstanceOf(MemberNotFoundException.class); - } - - @Test - void 이메일과_패스워드로_검증된_회원_아이디_검색하면_올바른_아이디가_반환된다() { - //given - String email = "test@google.com"; - String password = "test-password"; - given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) - .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); - - //when - Long id = memberService.findVerifiedMemberIdByEmailAndPassword( - email, password); - - //then - assertThat(id).isNotNull(); - } - - private Optional getVerifiedCheckDto(Boolean isVerified, String email, - String password) { - return Optional.of( - new EmailVerifiedCheckDto( - 1L, - isVerified, - email, - (password == null) ? null : passwordEncoder.encode(password) - ) - ); - } - - @Test - void 이메일과_패스워드로_검증되지_않은_회원_아이디_검색하면_예외가_발생한다() { - //given - String email = "test@google.com"; - String password = "test-password"; - given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) - .willReturn(getVerifiedCheckDto(Boolean.FALSE, email, password)); - - //when, then - assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password)) - .isExactlyInstanceOf(NotVerifiedMemberException.class); - } - - @Test - void 올바르지_않은_이메일로_검증된_회원_아이디_검색하면_예외가_발생한다() { - //given - String email = "test@google.com"; - String password = "test-password"; - given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) - .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); - - //when, then - assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email + "trash", password)) - .isExactlyInstanceOf(CredentialsNotMatchedException.class); - } - - @Test - void 올바르지_않은_비밀번호로_검증된_회원_아이디_검색하면_예외가_발생한다() { - //given - String email = "test@google.com"; - String password = "test-password"; - given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) - .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); - - //when, then - assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password + "trash")) - .isExactlyInstanceOf(CredentialsNotMatchedException.class); - } - - @Test - void 비밀번호가_없는_검증된_회원_아이디_검색하면_예외가_발생한다() { - //given - String email = "test@google.com"; - String password = "test-password"; - given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) - .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, null)); - - //when, then - assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email, password)) - .isExactlyInstanceOf(CredentialsNotMatchedException.class); - } - - @Test - void 올바르지_않은_이메일과_비밀번호로_검증된_회원_아이디_검색하면_예외가_발생한다() { - //given - String email = "test@google.com"; - String password = "test-password"; - given(memberRepository.findEmailVerifiedCheckDtoByEmail(anyString())) - .willReturn(getVerifiedCheckDto(Boolean.TRUE, email, password)); - - //when, then - assertThatThrownBy( - () -> memberService.findVerifiedMemberIdByEmailAndPassword(email + "password", - password + "trash")) - .isExactlyInstanceOf(CredentialsNotMatchedException.class); - } } From c861fc165ab42b5d8c8b0264d0ed6aa11e66742b Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 12:03:23 +0900 Subject: [PATCH 022/157] =?UTF-8?q?refact=20:=20member=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20mapper=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/api/MemberApiController.java | 14 +++- .../data/dto/MemberNotificationDto.java | 25 ++++++ .../member/data/mapper/MemberMapper.java | 78 ------------------- .../MemberNotificationSliceResponse.java | 14 ++++ .../domain/member/service/MemberService.java | 20 +---- .../security/oauth/dto/OAuthAttributes.java | 26 +++++-- .../service/CustomOAuth2UserService.java | 8 +- .../member/service/MemberServiceTest.java | 5 +- 8 files changed, 76 insertions(+), 114 deletions(-) delete mode 100644 backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java index aef134fff..44cb3c112 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java @@ -3,6 +3,7 @@ import jakarta.validation.Valid; import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -13,12 +14,11 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.member.data.dto.MemberDetailDto; -import site.timecapsulearchive.core.domain.member.data.reqeust.CheckEmailDuplicationRequest; +import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.reqeust.CheckStatusRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateFCMTokenRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateMemberDataRequest; import site.timecapsulearchive.core.domain.member.data.reqeust.UpdateNotificationEnabledRequest; -import site.timecapsulearchive.core.domain.member.data.response.CheckEmailDuplicationResponse; import site.timecapsulearchive.core.domain.member.data.response.MemberDetailResponse; import site.timecapsulearchive.core.domain.member.data.response.MemberNotificationSliceResponse; import site.timecapsulearchive.core.domain.member.data.response.MemberNotificationStatusResponse; @@ -26,6 +26,7 @@ import site.timecapsulearchive.core.domain.member.service.MemberService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; +import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; @RestController @RequiredArgsConstructor @@ -33,6 +34,7 @@ public class MemberApiController implements MemberApi { private final MemberService memberService; + private final S3PreSignedUrlManager s3PreSignedUrlManager; @GetMapping(produces = {"application/json"}) @Override @@ -112,10 +114,16 @@ public ResponseEntity> getMemberNotific @RequestParam(defaultValue = "20", value = "size") final int size, @RequestParam(value = "created_at") final ZonedDateTime createdAt ) { + final Slice memberNotificationDtos = memberService.findNotificationSliceByMemberId( + memberId, size, createdAt); + return ResponseEntity.ok( ApiSpec.success( SuccessCode.SUCCESS, - memberService.findNotificationSliceByMemberId(memberId, size, createdAt) + MemberNotificationSliceResponse.createOf( + memberNotificationDtos, + s3PreSignedUrlManager::getS3PreSignedUrlForGet + ) ) ); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberNotificationDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberNotificationDto.java index c2759cf97..29ba44bd3 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberNotificationDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberNotificationDto.java @@ -1,8 +1,11 @@ package site.timecapsulearchive.core.domain.member.data.dto; import java.time.ZonedDateTime; +import java.util.function.UnaryOperator; +import site.timecapsulearchive.core.domain.member.data.response.MemberNotificationResponse; import site.timecapsulearchive.core.domain.member.entity.CategoryName; import site.timecapsulearchive.core.domain.member.entity.NotificationStatus; +import site.timecapsulearchive.core.global.common.response.ResponseMappingConstant; public record MemberNotificationDto( String title, @@ -13,4 +16,26 @@ public record MemberNotificationDto( NotificationStatus status ) { + public MemberNotificationDto { + if (createdAt != null) { + createdAt = createdAt.withZoneSameInstant(ResponseMappingConstant.ZONE_ID); + } + } + + public MemberNotificationResponse toResponse(UnaryOperator getS3PreSignedUrlForGet) { + String notificationImageUrl = ""; + if (imageUrl != null) { + notificationImageUrl = getS3PreSignedUrlForGet.apply(imageUrl); + } + + return MemberNotificationResponse.builder() + .title(title) + .text(text) + .createdAt(createdAt) + .imageUrl(notificationImageUrl) + .categoryName(categoryName) + .status(status) + .build(); + } + } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java deleted file mode 100644 index b24b883ae..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/mapper/MemberMapper.java +++ /dev/null @@ -1,78 +0,0 @@ -package site.timecapsulearchive.core.domain.member.data.mapper; - -import java.time.ZoneId; -import java.util.List; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; -import site.timecapsulearchive.core.domain.member.data.response.MemberNotificationResponse; -import site.timecapsulearchive.core.domain.member.data.response.MemberNotificationSliceResponse; -import site.timecapsulearchive.core.domain.member.entity.Member; -import site.timecapsulearchive.core.domain.member.entity.SocialType; -import site.timecapsulearchive.core.global.security.oauth.dto.OAuth2UserInfo; -import site.timecapsulearchive.core.global.util.TagGenerator; -import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil; -import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; - -@Component -@RequiredArgsConstructor -public class MemberMapper { - - private static final ZoneId ASIA_SEOUL = ZoneId.of("Asia/Seoul"); - private final S3PreSignedUrlManager s3PreSignedUrlManager; - - public Member OAuthToEntity( - final String authId, - final SocialType socialType, - final OAuth2UserInfo oAuth2UserInfo - ) { - return Member.builder() - .authId(authId) - .nickname(MakeRandomNickNameUtil.makeRandomNickName()) - .email(oAuth2UserInfo.getEmail()) - .profileUrl(oAuth2UserInfo.getImageUrl()) - .socialType(socialType) - .tag(TagGenerator.generate(oAuth2UserInfo.getEmail(), socialType)) - .build(); - } - - public MemberNotificationSliceResponse notificationSliceToResponse( - final List content, - final boolean hasNext - ) { - List responses = content.stream() - .map(dto -> { - String imageUrl = ""; - if (dto.imageUrl() != null) { - imageUrl = s3PreSignedUrlManager.getS3PreSignedUrlForGet(dto.imageUrl()); - } - - return MemberNotificationResponse.builder() - .title(dto.title()) - .text(dto.text()) - .createdAt(dto.createdAt().withZoneSameInstant(ASIA_SEOUL)) - .imageUrl(imageUrl) - .categoryName(dto.categoryName()) - .status(dto.status()) - .build(); - } - ) - .toList(); - - return new MemberNotificationSliceResponse(responses, hasNext); - } - - public Member createMemberWithEmail(String email, String password) { - SocialType socialType = SocialType.EMAIL; - - return Member.builder() - .email(email) - .password(password) - .authId(String.valueOf(UUID.randomUUID())) - .profileUrl("") - .socialType(socialType) - .tag(TagGenerator.generate(email, socialType)) - .build(); - } -} \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberNotificationSliceResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberNotificationSliceResponse.java index 4ee3fe4dc..f365ae43d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberNotificationSliceResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberNotificationSliceResponse.java @@ -2,6 +2,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; +import java.util.function.UnaryOperator; +import org.springframework.data.domain.Slice; +import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; @Schema(description = "나의 알림 목록 응답") public record MemberNotificationSliceResponse( @@ -13,4 +16,15 @@ public record MemberNotificationSliceResponse( boolean hasNext ) { + public static MemberNotificationSliceResponse createOf( + final Slice memberNotificationDtos, + final UnaryOperator getS3PreSignedUrlForGet + ) { + final List responses = memberNotificationDtos.getContent() + .stream() + .map(dto -> dto.toResponse(getS3PreSignedUrlForGet)) + .toList(); + + return new MemberNotificationSliceResponse(responses, memberNotificationDtos.hasNext()); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 1aa278b29..bc5cdd464 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -4,25 +4,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Slice; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import site.timecapsulearchive.core.domain.member.data.dto.EmailVerifiedCheckDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberDetailDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.dto.SignUpRequestDto; import site.timecapsulearchive.core.domain.member.data.dto.UpdateMemberDataDto; import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; -import site.timecapsulearchive.core.domain.member.data.mapper.MemberMapper; -import site.timecapsulearchive.core.domain.member.data.response.CheckEmailDuplicationResponse; -import site.timecapsulearchive.core.domain.member.data.response.MemberNotificationSliceResponse; import site.timecapsulearchive.core.domain.member.data.response.MemberNotificationStatusResponse; import site.timecapsulearchive.core.domain.member.data.response.MemberStatusResponse; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.entity.MemberTemporary; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.domain.member.exception.AlreadyVerifiedException; -import site.timecapsulearchive.core.domain.member.exception.CredentialsNotMatchedException; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.exception.NotVerifiedMemberException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; @@ -38,8 +32,6 @@ public class MemberService { private final MemberRepository memberRepository; private final MemberTemporaryRepository memberTemporaryRepository; - private final MemberMapper memberMapper; - @Transactional public Long createMember(final SignUpRequestDto dto) { final String tag = TagGenerator.generate(dto.email(), dto.socialType()); @@ -147,18 +139,12 @@ public void updateMemberNotificationEnabled( } } - public MemberNotificationSliceResponse findNotificationSliceByMemberId( + public Slice findNotificationSliceByMemberId( final Long memberId, final int size, final ZonedDateTime createdAt ) { - final Slice notifications = memberRepository.findNotificationSliceByMemberId( - memberId, size, createdAt); - - return memberMapper.notificationSliceToResponse( - notifications.getContent(), - notifications.hasNext() - ); + return memberRepository.findNotificationSliceByMemberId(memberId, size, createdAt); } public MemberNotificationStatusResponse checkNotificationStatus(final Long memberId) { @@ -179,7 +165,7 @@ public void updateMemberData( final UpdateMemberDataDto updateMemberDataDto ) { final int updateMemberData = memberRepository.updateMemberData(memberId, - updateMemberDataDto.nickname(), updateMemberDataDto.tag()); + updateMemberDataDto.nickname(), updateMemberDataDto.tag()); if (updateMemberData != 1) { throw new MemberNotFoundException(); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuthAttributes.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuthAttributes.java index 3662e73b3..2b80cef91 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuthAttributes.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuthAttributes.java @@ -3,21 +3,24 @@ import java.util.Map; import lombok.Builder; import lombok.Getter; +import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.global.security.oauth.dto.google.GoogleOAuth2UserInfo; import site.timecapsulearchive.core.global.security.oauth.dto.kakao.KakaoOAuth2UserInfo; +import site.timecapsulearchive.core.global.util.TagGenerator; +import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil; @Getter public class OAuthAttributes { private final String authId; - private final OAuth2UserInfo oauth2UserInfo; + private final OAuth2UserInfo OAuth2UserInfo; @Builder - private OAuthAttributes(String authId, OAuth2UserInfo oauth2UserInfo) { + private OAuthAttributes(String authId, OAuth2UserInfo OAuth2UserInfo) { this.authId = authId; - this.oauth2UserInfo = oauth2UserInfo; + this.OAuth2UserInfo = OAuth2UserInfo; } public static OAuthAttributes of( @@ -40,7 +43,7 @@ private static OAuthAttributes ofKakao( return OAuthAttributes.builder() .authId(String.valueOf(authId)) - .oauth2UserInfo(new KakaoOAuth2UserInfo(attributes)) + .OAuth2UserInfo(new KakaoOAuth2UserInfo(attributes)) .build(); } @@ -50,7 +53,20 @@ public static OAuthAttributes ofGoogle( ) { return OAuthAttributes.builder() .authId((String) attributes.get(userNameAttributeName)) - .oauth2UserInfo(new GoogleOAuth2UserInfo(attributes)) + .OAuth2UserInfo(new GoogleOAuth2UserInfo(attributes)) + .build(); + } + + public Member OAuthToMember( + final SocialType socialType + ) { + return Member.builder() + .authId(authId) + .nickname(MakeRandomNickNameUtil.makeRandomNickName()) + .email(OAuth2UserInfo.getEmail()) + .profileUrl(OAuth2UserInfo.getImageUrl()) + .socialType(socialType) + .tag(TagGenerator.generate(OAuth2UserInfo.getEmail(), socialType)) .build(); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java index 4030aca79..8b5ab1618 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java @@ -11,7 +11,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import site.timecapsulearchive.core.domain.member.data.mapper.MemberMapper; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.entity.SocialType; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; @@ -24,7 +23,6 @@ public class CustomOAuth2UserService implements OAuth2UserService { private final MemberRepository memberRepository; - private final MemberMapper memberMapper; @Override public OAuth2User loadUser(final OAuth2UserRequest userRequest) @@ -71,11 +69,7 @@ private Member getMember(final OAuthAttributes attributes, final SocialType soci } private Member saveMember(final SocialType socialType, final OAuthAttributes attributes) { - final Member createMember = memberMapper.OAuthToEntity( - attributes.getAuthId(), - socialType, - attributes.getOauth2UserInfo() - ); + final Member createMember = attributes.OAuthToMember(socialType); boolean isDuplicateTag = memberRepository.checkTagDuplication(createMember.getTag()); if (isDuplicateTag) { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java index 49a51b33f..fd81ac9d4 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java @@ -2,7 +2,6 @@ import static org.mockito.Mockito.mock; -import site.timecapsulearchive.core.domain.member.data.mapper.MemberMapper; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; @@ -11,11 +10,9 @@ class MemberServiceTest { private final MemberRepository memberRepository = mock(MemberRepository.class); private final MemberTemporaryRepository memberTemporaryRepository = mock( MemberTemporaryRepository.class); - private final MemberMapper memberMapper = mock(MemberMapper.class); private final MemberService memberService = new MemberService( memberRepository, - memberTemporaryRepository, - memberMapper + memberTemporaryRepository ); } From 7c26a4e30ae7bbd254ced480193293c2eee22b12 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 12:10:08 +0900 Subject: [PATCH 023/157] =?UTF-8?q?fix=20:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberQueryRepository.java | 7 ------ .../repository/MemberQueryRepositoryImpl.java | 25 +++---------------- .../repository/MemberQueryRepositoryTest.java | 24 ------------------ 3 files changed, 4 insertions(+), 52 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java index 8d6904371..7cf1d3363 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Optional; import org.springframework.data.domain.Slice; -import site.timecapsulearchive.core.domain.member.data.dto.EmailVerifiedCheckDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberDetailDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; @@ -31,12 +30,6 @@ Slice findNotificationSliceByMemberId( final ZonedDateTime createdAt ); - List findMemberNotificationDtos( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ); - Optional findIsAlarmByMemberId(final Long memberId); List findMemberIdsByIds(final List ids); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java index 7f060ab40..919e31bf4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java @@ -14,15 +14,13 @@ import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Repository; -import site.timecapsulearchive.core.domain.member.data.dto.EmailVerifiedCheckDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberDetailDto; import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; import site.timecapsulearchive.core.domain.member.entity.SocialType; +import site.timecapsulearchive.core.global.util.SliceUtil; @Repository @RequiredArgsConstructor @@ -94,24 +92,7 @@ public Slice findNotificationSliceByMemberId( final int size, final ZonedDateTime createdAt ) { - final List notifications = findMemberNotificationDtos( - memberId, size, createdAt); - - final boolean hasNext = notifications.size() > size; - if (hasNext) { - notifications.remove(size); - } - - return new SliceImpl<>(notifications, Pageable.ofSize(size), hasNext); - } - - @Override - public List findMemberNotificationDtos( - final Long memberId, - final int size, - final ZonedDateTime createdAt - ) { - return query + final List memberNotificationDtos = query .select( Projections.constructor( MemberNotificationDto.class, @@ -129,6 +110,8 @@ public List findMemberNotificationDtos( .orderBy(notification.id.desc()) .limit(size + 1) .fetch(); + + return SliceUtil.makeSlice(size, memberNotificationDtos); } @Override diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java index 9ddf1e92f..ad71f640c 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java @@ -76,30 +76,6 @@ void setup(@Autowired EntityManager entityManager) { } } - @Test - void 중복_이메일로_중복_체크하면_True가_반환된다() { - //given - String duplicatedEmail = "1test@google.com"; - - //when - Boolean isDuplicated = memberQueryRepository.checkEmailDuplication(duplicatedEmail); - - //then - assertThat(isDuplicated).isTrue(); - } - - @Test - void 고유한_이메일로_중복_체크_테스트하면_False가_반환된다() { - //given - String uniqueEmail = "unique@google.com"; - - //when - Boolean isDuplicated = memberQueryRepository.checkEmailDuplication(uniqueEmail); - - //then - assertThat(isDuplicated).isFalse(); - } - @Test void 특정_사용자로_알림_목록을_조회하면_알림_리스트가_반환된다() { int size = 20; From a4e042289cb5f0f6b06401e84ea10ab191b18922 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 15:39:43 +0900 Subject: [PATCH 024/157] =?UTF-8?q?test=20:=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/member/entity/SocialType.java | 2 +- .../member/service/MemberServiceTest.java | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/SocialType.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/SocialType.java index ad1591e91..59685e361 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/SocialType.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/SocialType.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; public enum SocialType { - KAKAO, GOOGLE, EMAIL; + KAKAO, GOOGLE; @JsonCreator public static SocialType from(String s) { diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java index fd81ac9d4..b2bb64949 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java @@ -1,9 +1,19 @@ package site.timecapsulearchive.core.domain.member.service; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; +import site.timecapsulearchive.core.domain.member.data.response.MemberStatusResponse; +import site.timecapsulearchive.core.domain.member.entity.SocialType; +import site.timecapsulearchive.core.domain.member.exception.NotVerifiedMemberException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; +import site.timecapsulearchive.core.global.error.ErrorCode; class MemberServiceTest { @@ -15,4 +25,50 @@ class MemberServiceTest { memberRepository, memberTemporaryRepository ); + + @Test + void 인증된_사용자는_전화번호_인증을_했다() { + //given + String authId = "testAuthId"; + SocialType socialType = SocialType.KAKAO; + given(memberRepository.findIsVerifiedByAuthIdAndSocialType(authId, socialType)).willReturn( + Boolean.TRUE); + + //when + MemberStatusResponse memberStatusResponse = memberService.checkStatus(authId, socialType); + + //then + assertThat(memberStatusResponse.isVerified()).isTrue(); + } + + @Test + void 인증되지_않은_사용자는_전화번호_인증을_하지_않았다() { + //given + String authId = "testAuthId"; + SocialType socialType = SocialType.KAKAO; + given(memberRepository.findIsVerifiedByAuthIdAndSocialType(authId, socialType)).willReturn( + Boolean.FALSE); + + //when + MemberStatusResponse memberStatusResponse = memberService.checkStatus(authId, socialType); + + //then + assertThat(memberStatusResponse.isVerified()).isFalse(); + } + + @Test + void 로그인을_할_때_인증되지_않은_사용자는_예외가_발생한다() { + //given + String authId = "testAuthId"; + SocialType socialType = SocialType.KAKAO; + VerifiedCheckDto verifiedCheckDto = new VerifiedCheckDto(1L, false); + given(memberRepository.findVerifiedCheckDtoByAuthIdAndSocialType(authId, + socialType)).willReturn(Optional.of(verifiedCheckDto)); + + //when + assertThatThrownBy(() -> memberService.findVerifiedMemberIdByAuthIdAndSocialType( + authId, socialType)) + .isInstanceOf(NotVerifiedMemberException.class) + .hasMessageContaining(ErrorCode.LOGIN_ON_NOT_VERIFIED_ERROR.getMessage()); + } } From 5a4fbb9e0376c8e51d34e8a83a626946bf305d9e Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 16:01:39 +0900 Subject: [PATCH 025/157] =?UTF-8?q?refact=20:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group_invite_repository/GroupInviteQueryRepositoryImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java index ba46a04bb..4305daacb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/group_invite_repository/GroupInviteQueryRepositoryImpl.java @@ -99,6 +99,7 @@ public Slice findGroupReceivingInvitesSlice( .join(groupInvite.group, group) .join(groupInvite.groupOwner, member) .where(groupInvite.groupMember.id.eq(memberId).and(groupInvite.createdAt.lt(createdAt))) + .orderBy(groupInvite.id.desc()) .limit(size + 1) .fetch(); From 03561a5f9ba13bdf27427d12e6552046906fd85f Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 16:38:11 +0900 Subject: [PATCH 026/157] =?UTF-8?q?refact=20:=20=EC=A3=BC=EC=86=8C?= =?UTF-8?q?=EB=A1=9D=20=EA=B8=B0=EB=B0=98=20=EC=B9=9C=EA=B5=AC=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=8B=9C=20=EB=B3=B8=EC=9D=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/command/FriendCommandApiController.java | 2 -- .../data/request/SearchFriendsRequest.java | 4 ++-- .../service/query/FriendQueryService.java | 17 +++++++++++++++-- .../repository/MemberQueryRepository.java | 6 ++++-- .../repository/MemberQueryRepositoryImpl.java | 13 +++++++++++-- .../service/query/FriendQueryServiceTest.java | 4 ++++ 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java index 5ccc2e626..d8ab31bfa 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/api/command/FriendCommandApiController.java @@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.RestController; import site.timecapsulearchive.core.domain.friend.data.request.SendFriendRequest; import site.timecapsulearchive.core.domain.friend.service.command.FriendCommandService; -import site.timecapsulearchive.core.domain.member.service.MemberService; import site.timecapsulearchive.core.global.common.response.ApiSpec; import site.timecapsulearchive.core.global.common.response.SuccessCode; @@ -21,7 +20,6 @@ public class FriendCommandApiController implements FriendCommandApi { private final FriendCommandService friendCommandService; - private final MemberService memberService; @DeleteMapping(value = "/{friend_id}") @Override diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/SearchFriendsRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/SearchFriendsRequest.java index 52161b5db..1e898aedf 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/SearchFriendsRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/data/request/SearchFriendsRequest.java @@ -3,7 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.function.Function; +import java.util.function.UnaryOperator; import site.timecapsulearchive.core.domain.friend.data.dto.PhoneBook; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @@ -15,7 +15,7 @@ public record SearchFriendsRequest( ) { public List toPhoneEncryption( - final Function hashEncryptionFunction + final UnaryOperator hashEncryptionFunction ) { return phoneBooks.stream() .map(phoneBook -> new ByteArrayWrapper(hashEncryptionFunction.apply( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index 15e711f91..233c6ef33 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -15,6 +15,8 @@ import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteRepository; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; +import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @@ -24,6 +26,7 @@ @RequiredArgsConstructor public class FriendQueryService { + private final MemberRepository memberRepository; private final MemberFriendRepository memberFriendRepository; private final MemberGroupRepository memberGroupRepository; private final GroupInviteRepository groupInviteRepository; @@ -91,10 +94,20 @@ public List findFriendsByPhone( final Long memberId, final List phoneEncryption ) { - final List hashes = phoneEncryption.stream().map(ByteArrayWrapper::getData) + final List hashes = phoneEncryption.stream() + .map(ByteArrayWrapper::getData) .toList(); - return memberFriendRepository.findFriendsByPhone(memberId, hashes); + final byte[] memberPhoneHash = memberRepository.findMemberPhoneHash(memberId).orElseThrow( + MemberNotFoundException::new); + final ByteArrayWrapper memberPhoneWrapper = new ByteArrayWrapper(memberPhoneHash); + + final List friendSummaryDtos = memberFriendRepository.findFriendsByPhone( + memberId, hashes); + + friendSummaryDtos.removeIf(dto -> dto.phoneHash().equals(memberPhoneWrapper)); + + return friendSummaryDtos; } public SearchFriendSummaryDtoByTag searchFriend(final Long memberId, final String tag) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java index 13827ea1f..a7be5d210 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java @@ -43,7 +43,9 @@ List findMemberNotificationDtos( Optional findIsAlarmByMemberId(final Long memberId); - List findMemberIdsByIds(List ids); + List findMemberIdsByIds(final List ids); - boolean checkTagDuplication(String tag); + boolean checkTagDuplication(final String tag); + + Optional findMemberPhoneHash(final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java index 0afced1fb..547d1e2a7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java @@ -173,7 +173,7 @@ public Optional findIsAlarmByMemberId(final Long memberId) { } @Override - public List findMemberIdsByIds(List ids) { + public List findMemberIdsByIds(final List ids) { return query .select(member.id) .from(member) @@ -182,7 +182,7 @@ public List findMemberIdsByIds(List ids) { } @Override - public boolean checkTagDuplication(String tag) { + public boolean checkTagDuplication(final String tag) { final Integer count = query.selectOne() .from(member) .where(member.tag.eq(tag)) @@ -190,4 +190,13 @@ public boolean checkTagDuplication(String tag) { return count != null; } + + @Override + public Optional findMemberPhoneHash(final Long memberId) { + return Optional.ofNullable(query.select(member.phone_hash) + .from(member) + .where(member.id.eq(memberId)) + .fetchOne() + ); + } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java index 4596edcc3..7390c8e0e 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java @@ -27,12 +27,15 @@ import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException; import site.timecapsulearchive.core.domain.friend.repository.friend_invite.FriendInviteRepository; import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; import site.timecapsulearchive.core.domain.member_group.repository.group_invite_repository.GroupInviteRepository; import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; class FriendQueryServiceTest { + private final MemberRepository memberRepository = mock( + MemberRepository.class); private final MemberFriendRepository memberFriendRepository = mock( MemberFriendRepository.class); private final MemberGroupRepository memberGroupRepository = mock(MemberGroupRepository.class); @@ -41,6 +44,7 @@ class FriendQueryServiceTest { FriendInviteRepository.class); private final FriendQueryService friendQueryService = new FriendQueryService( + memberRepository, memberFriendRepository, memberGroupRepository, groupInviteRepository, From 3d2edd7abccfa26ca1e682582716c3c61b74a2d2 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 17:14:24 +0900 Subject: [PATCH 027/157] =?UTF-8?q?test=20:=20=EC=A3=BC=EC=86=8C=EB=A1=9D?= =?UTF-8?q?=20=EA=B8=B0=EB=B0=98=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20?= =?UTF-8?q?=EB=B3=B8=EC=9D=B8=EC=9D=80=20=EC=A1=B0=ED=9A=8C=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/query/FriendQueryService.java | 9 +++--- .../service/query/FriendQueryServiceTest.java | 29 ++++++++++++++++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index 233c6ef33..73e6cc88b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -1,6 +1,7 @@ package site.timecapsulearchive.core.domain.friend.service.query; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; @@ -100,12 +101,12 @@ public List findFriendsByPhone( final byte[] memberPhoneHash = memberRepository.findMemberPhoneHash(memberId).orElseThrow( MemberNotFoundException::new); - final ByteArrayWrapper memberPhoneWrapper = new ByteArrayWrapper(memberPhoneHash); + final ByteArrayWrapper myPhoneWrapper = new ByteArrayWrapper(memberPhoneHash); - final List friendSummaryDtos = memberFriendRepository.findFriendsByPhone( - memberId, hashes); + final List friendSummaryDtos = new ArrayList<>(memberFriendRepository.findFriendsByPhone( + memberId, hashes)); - friendSummaryDtos.removeIf(dto -> dto.phoneHash().equals(memberPhoneWrapper)); + friendSummaryDtos.removeIf(dto -> dto.phoneHash().equals(myPhoneWrapper)); return friendSummaryDtos; } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java index 7390c8e0e..fab2bf8b0 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java @@ -52,7 +52,7 @@ class FriendQueryServiceTest { ); @Test - void 사용자는_주소록_기반_핸드폰_번호로_Ahchive_사용자_리스트를_조회_할_수_있다() { + void 사용자는_주소록_기반_핸드폰_번호로_ARchive_사용자_리스트를_조회_할_수_있다() { //given Long memberId = 1L; List phones = MemberFixture.getPhones(5); @@ -69,7 +69,7 @@ class FriendQueryServiceTest { @Test - void 사용자는_주소록_기반_번호_없이_Ahchive_사용자_리스트_조회하면_빈_리스트를_반환한다() { + void 사용자는_주소록_기반_핸드폰_번호_없이_ARchive_사용자_리스트_조회하면_빈_리스트를_반환한다() { //given Long memberId = 1L; List phones = Collections.emptyList(); @@ -85,7 +85,28 @@ class FriendQueryServiceTest { } @Test - void 사용자는_태그로_Ahchive_사용자를_검색할_수_있다() { + void 사용자는_주소록_기반_번호로_ARchive_사용자를_검색하면_본인은_조회되지_않는다() { + //given + Long memberId = 1L; + int friendsCount = 5; + int startFriendsIndex = 0; + + List phones = MemberFixture.getPhones(friendsCount); + + given(memberRepository.findMemberPhoneHash(anyLong())).willReturn( + Optional.of(MemberFixture.getPhoneBytes(startFriendsIndex))); + + given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) + .willReturn(FriendDtoFixture.getFriendSummaryDtos(friendsCount)); + + List dtos = friendQueryService.findFriendsByPhone(memberId, + phones); + + assertThat(dtos.size()).isEqualTo(friendsCount - 1); + } + + @Test + void 사용자는_태그로_ARchive_사용자를_검색할_수_있다() { //given Long memberId = 1L; String tag = "testTag"; @@ -109,7 +130,7 @@ class FriendQueryServiceTest { } @Test - void 사용자는_존재하지_않는_태그로_Ahchive_사용자를_검색하면_예외가_발생한다() { + void 사용자는_존재하지_않는_태그로_ARchive_사용자를_검색하면_예외가_발생한다() { //given Long memberId = 1L; String tag = "testTag"; From 20248313d53d7aba202b36d6c4ba0f05f5344a05 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 17:18:54 +0900 Subject: [PATCH 028/157] =?UTF-8?q?fix=20:=20=EB=B3=B4=EB=AC=BC=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=20=EC=97=B4=EB=9E=8C=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/TreasureCapsuleService.java | 6 ------ .../rabbitmq/RabbitFailedComponentConfig.java | 21 ------------------- .../rabbitmq/RabbitmqComponentConstants.java | 7 +------ .../config/rabbitmq/RabbitmqConfig.java | 21 ------------------- .../manager/SocialNotificationManager.java | 17 --------------- 5 files changed, 1 insertion(+), 71 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleService.java index 70425bc4c..58dd74791 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleService.java @@ -15,7 +15,6 @@ import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.domain.member.exception.MemberNotFoundException; import site.timecapsulearchive.core.domain.member.repository.MemberRepository; -import site.timecapsulearchive.core.infra.queue.manager.SocialNotificationManager; @Service @RequiredArgsConstructor @@ -24,7 +23,6 @@ public class TreasureCapsuleService { private final MemberRepository memberRepository; private final CapsuleRepository capsuleRepository; private final CapsuleSkinRepository capsuleSkinRepository; - private final SocialNotificationManager socialNotificationManager; private final TransactionTemplate transactionTemplate; public TreasureCapsuleOpenDto openTreasureCapsule(final Long memberId, final Long capsuleId) { @@ -48,10 +46,6 @@ public TreasureCapsuleOpenDto openTreasureCapsule(final Long memberId, final Lon return imageUrl; }); - // 알림 전송 - socialNotificationManager.sendTreasureCaptureMessage(memberId, member.getNickname(), - treasureImageUrl); - return new TreasureCapsuleOpenDto(treasureImageUrl); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java index 10507f188..82dd8241b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitFailedComponentConfig.java @@ -110,25 +110,4 @@ public Binding friendAcceptFailBinding() { .to(friendAcceptFailExchange()) .withQueueName(); } - - @Bean - public Queue treasureCaptureFailQueue() { - return new Queue( - RabbitmqComponentConstants.TREASURE_CAPTURE_NOTIFICATION_QUEUE.getFailComponent(), - true); - } - - @Bean - public DirectExchange treasureCaptureFailExchange() { - return new DirectExchange( - RabbitmqComponentConstants.TREASURE_CAPTURE_NOTIFICATION_EXCHANGE.getFailComponent()); - } - - @Bean - public Binding treasureCaptureFailBinding() { - return BindingBuilder - .bind(treasureCaptureFailQueue()) - .to(treasureCaptureFailExchange()) - .withQueueName(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java index fd00b378b..4f615029c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqComponentConstants.java @@ -25,12 +25,7 @@ public enum RabbitmqComponentConstants { GROUP_ACCEPT_NOTIFICATION_QUEUE("notification.groupAccept.queue", "fail.notification.groupAccept.queue"), GROUP_ACCEPT_NOTIFICATION_EXCHANGE("notification.groupAccept.exchange", - "fail.notification.groupAccept.exchange"), - - TREASURE_CAPTURE_NOTIFICATION_QUEUE("notification.treasureCapture.queue", - "fail.notification.treasureCapture.queue"), - TREASURE_CAPTURE_NOTIFICATION_EXCHANGE("notification.treasureCapture.exchange", - "fail.notification.treasureCapture.exchange"); + "fail.notification.groupAccept.exchange"); private final String successComponent; private final String failComponent; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java index e30fb1aef..8343c1f3a 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/rabbitmq/RabbitmqConfig.java @@ -123,27 +123,6 @@ public Binding friendAcceptBinding() { .withQueueName(); } - @Bean - public Queue treasureCaptureQueue() { - return new Queue( - RabbitmqComponentConstants.TREASURE_CAPTURE_NOTIFICATION_QUEUE.getSuccessComponent(), - true); - } - - @Bean - public DirectExchange treasureCaptureExchange() { - return new DirectExchange( - RabbitmqComponentConstants.TREASURE_CAPTURE_NOTIFICATION_EXCHANGE.getSuccessComponent()); - } - - @Bean - public Binding treasureCaptureBinding() { - return BindingBuilder - .bind(treasureCaptureQueue()) - .to(treasureCaptureExchange()) - .withQueueName(); - } - @Bean public RabbitTemplate publisherConfirmsRabbitTemplate() { RabbitTemplate rabbitTemplate = new RabbitTemplate(publisherConfirmsConnectionFactory()); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java index 3eb9b11f5..888748a02 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/infra/queue/manager/SocialNotificationManager.java @@ -10,15 +10,12 @@ import site.timecapsulearchive.core.infra.queue.data.dto.FriendsReqNotificationsDto; import site.timecapsulearchive.core.infra.queue.data.dto.GroupAcceptNotificationDto; import site.timecapsulearchive.core.infra.queue.data.dto.GroupInviteNotificationDto; -import site.timecapsulearchive.core.infra.queue.data.dto.TreasureCaptureNotificationDto; -import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager; @Component @RequiredArgsConstructor public class SocialNotificationManager { private final RabbitTemplate basicRabbitTemplate; - private final S3PreSignedUrlManager s3PreSignedUrlManager; /** * 단건의 친구 요청을 받아서 알림 전송을 요청한다 @@ -91,18 +88,4 @@ public void sendGroupAcceptMessage( GroupAcceptNotificationDto.createOf(targetId, groupMemberNickname) ); } - - public void sendTreasureCaptureMessage( - final Long targetId, - final String memberNickname, - final String treasureImageUrl - ) { - String preSignedUrl = s3PreSignedUrlManager.getS3PreSignedUrlForGet(treasureImageUrl); - - basicRabbitTemplate.convertAndSend( - RabbitmqComponentConstants.TREASURE_CAPTURE_NOTIFICATION_EXCHANGE.getSuccessComponent(), - RabbitmqComponentConstants.TREASURE_CAPTURE_NOTIFICATION_QUEUE.getSuccessComponent(), - TreasureCaptureNotificationDto.createOf(targetId, preSignedUrl, memberNickname) - ); - } } From 4edb6f75dc8402cf59166716668f9fbdc9d9caf6 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 19:03:50 +0900 Subject: [PATCH 029/157] =?UTF-8?q?test=20:=20=EC=BA=A1=EC=8A=90=20?= =?UTF-8?q?=EA=B0=9C=EB=B4=89=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/fixture/domain/CapsuleFixture.java | 3 +- .../service/TreasureCapsuleServiceTest.java | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleServiceTest.java diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java index f2ea71594..5f0589baf 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java @@ -123,8 +123,7 @@ private static CapsuleBuilder getCapsuleBuilder(Long memberId) { CapsuleSkin capsuleSkin = CapsuleSkinFixture.capsuleSkin(member); Group group = GroupFixture.group(); - CapsuleBuilder capsuleBuilder = defaultGroupCapsuleBuilder(member, capsuleSkin, group); - return capsuleBuilder; + return defaultGroupCapsuleBuilder(member, capsuleSkin, group); } public static Optional groupCapsuleNotAllMemberOpen( diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleServiceTest.java new file mode 100644 index 000000000..c59062597 --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/treasure_capsule/service/TreasureCapsuleServiceTest.java @@ -0,0 +1,54 @@ +package site.timecapsulearchive.core.domain.capsule.treasure_capsule.service; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.springframework.transaction.support.TransactionTemplate; +import site.timecapsulearchive.core.common.dependency.TestTransactionTemplate; +import site.timecapsulearchive.core.common.fixture.domain.CapsuleFixture; +import site.timecapsulearchive.core.common.fixture.domain.CapsuleSkinFixture; +import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; +import site.timecapsulearchive.core.domain.capsule.entity.Capsule; +import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; +import site.timecapsulearchive.core.domain.capsule.exception.CapsuleNotFondException; +import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.capsule.CapsuleRepository; +import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; +import site.timecapsulearchive.core.domain.capsuleskin.repository.CapsuleSkinRepository; +import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member.repository.MemberRepository; +import site.timecapsulearchive.core.global.error.ErrorCode; + +class TreasureCapsuleServiceTest { + + private final MemberRepository memberRepository = mock(MemberRepository.class); + private final CapsuleRepository capsuleRepository = mock(CapsuleRepository.class); + private final CapsuleSkinRepository capsuleSkinRepository = mock(CapsuleSkinRepository.class); + private final TransactionTemplate transactionTemplate = TestTransactionTemplate.spied(); + + TreasureCapsuleService treasureCapsuleService = new TreasureCapsuleService( + memberRepository, capsuleRepository, capsuleSkinRepository, transactionTemplate); + + + @Test + void 보물_캡슐_개봉_시_이미_개봉되었다면_예외가_발생한다() { + Member member = MemberFixture.member(0); + CapsuleSkin capsuleSkin = CapsuleSkinFixture.capsuleSkin(member); + CapsuleType capsuleType = CapsuleType.TREASURE; + Capsule capsule = CapsuleFixture.capsule(member, capsuleSkin, capsuleType); + + Long memberId = 1L; + Long capsuleId = 1L; + + given(memberRepository.findMemberById(memberId)).willReturn(Optional.of(member)); + given(capsuleRepository.findCapsuleWithImageByCapsuleId(capsuleId)) + .willThrow(new CapsuleNotFondException()); + + assertThatThrownBy(() -> treasureCapsuleService.openTreasureCapsule(memberId, capsuleId)) + .isInstanceOf(CapsuleNotFondException.class) + .hasMessage(ErrorCode.CAPSULE_NOT_FOUND_ERROR.getMessage()); + } + +} From e67b462775fc8d1d197359d8bedd5bd1a3b07fce Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Sun, 23 Jun 2024 19:17:36 +0900 Subject: [PATCH 030/157] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../friend/service/query/FriendQueryServiceTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java index fab2bf8b0..0339da230 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java @@ -55,8 +55,11 @@ class FriendQueryServiceTest { void 사용자는_주소록_기반_핸드폰_번호로_ARchive_사용자_리스트를_조회_할_수_있다() { //given Long memberId = 1L; + int startFriendsIndex = 0; List phones = MemberFixture.getPhones(5); + given(memberRepository.findMemberPhoneHash(anyLong())).willReturn( + Optional.of(MemberFixture.getPhoneBytes(startFriendsIndex))); given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) .willReturn(FriendDtoFixture.getFriendSummaryDtos(5)); @@ -64,7 +67,7 @@ class FriendQueryServiceTest { List dtos = friendQueryService.findFriendsByPhone(memberId, phones); //then - assertThat(dtos.size()).isEqualTo(phones.size()); + assertThat(dtos).isNotEmpty(); } @@ -72,7 +75,11 @@ class FriendQueryServiceTest { void 사용자는_주소록_기반_핸드폰_번호_없이_ARchive_사용자_리스트_조회하면_빈_리스트를_반환한다() { //given Long memberId = 1L; + int startFriendsIndex = 0; List phones = Collections.emptyList(); + + given(memberRepository.findMemberPhoneHash(anyLong())).willReturn( + Optional.of(MemberFixture.getPhoneBytes(startFriendsIndex))); given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) .willReturn(Collections.emptyList()); From 949ed60732cd4aa75fe05b3e0062f66ed8a24751 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 24 Jun 2024 00:04:21 +0900 Subject: [PATCH 031/157] =?UTF-8?q?refact=20:=20wrapper=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=90=EC=8B=B8=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/query/FriendQueryService.java | 12 +++--- .../repository/MemberQueryRepository.java | 3 +- .../repository/MemberQueryRepositoryImpl.java | 37 ++++++++++++------- .../common/wrapper/ByteArrayWrapper.java | 11 +----- .../common/fixture/domain/MemberFixture.java | 4 ++ .../service/query/FriendQueryServiceTest.java | 6 +-- 6 files changed, 40 insertions(+), 33 deletions(-) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java index 73e6cc88b..7fddb7169 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryService.java @@ -96,15 +96,15 @@ public List findFriendsByPhone( final List phoneEncryption ) { final List hashes = phoneEncryption.stream() - .map(ByteArrayWrapper::getData) + .map(ByteArrayWrapper::data) .toList(); - final byte[] memberPhoneHash = memberRepository.findMemberPhoneHash(memberId).orElseThrow( - MemberNotFoundException::new); - final ByteArrayWrapper myPhoneWrapper = new ByteArrayWrapper(memberPhoneHash); + final ByteArrayWrapper myPhoneWrapper = memberRepository.findMemberPhoneHash(memberId) + .orElseThrow(MemberNotFoundException::new); - final List friendSummaryDtos = new ArrayList<>(memberFriendRepository.findFriendsByPhone( - memberId, hashes)); + final List friendSummaryDtos = new ArrayList<>( + memberFriendRepository.findFriendsByPhone( + memberId, hashes)); friendSummaryDtos.removeIf(dto -> dto.phoneHash().equals(myPhoneWrapper)); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java index a7be5d210..c7fbfac2d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepository.java @@ -9,6 +9,7 @@ import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; import site.timecapsulearchive.core.domain.member.entity.SocialType; +import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; public interface MemberQueryRepository { @@ -47,5 +48,5 @@ List findMemberNotificationDtos( boolean checkTagDuplication(final String tag); - Optional findMemberPhoneHash(final Long memberId); + Optional findMemberPhoneHash(final Long memberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java index 547d1e2a7..c285b1b76 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java @@ -23,19 +23,20 @@ import site.timecapsulearchive.core.domain.member.data.dto.MemberNotificationDto; import site.timecapsulearchive.core.domain.member.data.dto.VerifiedCheckDto; import site.timecapsulearchive.core.domain.member.entity.SocialType; +import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper; @Repository @RequiredArgsConstructor public class MemberQueryRepositoryImpl implements MemberQueryRepository { - private final JPAQueryFactory query; + private final JPAQueryFactory jpaQueryFactory; @Override public Boolean findIsVerifiedByAuthIdAndSocialType( final String authId, final SocialType socialType ) { - return query.select(member.isVerified) + return jpaQueryFactory.select(member.isVerified) .from(member) .where(member.authId.eq(authId).and(member.socialType.eq(socialType))) .fetchOne(); @@ -47,7 +48,7 @@ public Optional findVerifiedCheckDtoByAuthIdAndSocialType( final SocialType socialType ) { return Optional.ofNullable( - query + jpaQueryFactory .select( Projections.constructor( VerifiedCheckDto.class, @@ -64,7 +65,7 @@ public Optional findVerifiedCheckDtoByAuthIdAndSocialType( @Override public Optional findMemberDetailResponseDtoById(final Long memberId) { return Optional.ofNullable( - query + jpaQueryFactory .select( Projections.constructor( MemberDetailDto.class, @@ -111,7 +112,7 @@ public List findMemberNotificationDtos( final int size, final ZonedDateTime createdAt ) { - return query + return jpaQueryFactory .select( Projections.constructor( MemberNotificationDto.class, @@ -136,7 +137,7 @@ public Optional findEmailVerifiedCheckDtoByEmail( final String email ) { return Optional.ofNullable( - query + jpaQueryFactory .select( Projections.constructor( EmailVerifiedCheckDto.class, @@ -154,7 +155,7 @@ public Optional findEmailVerifiedCheckDtoByEmail( @Override public Boolean checkEmailDuplication(final String email) { - final Integer count = query.selectOne() + final Integer count = jpaQueryFactory.selectOne() .from(member) .where(member.email.eq(email)) .fetchFirst(); @@ -165,7 +166,7 @@ public Boolean checkEmailDuplication(final String email) { @Override public Optional findIsAlarmByMemberId(final Long memberId) { return Optional.ofNullable( - query.select(member.notificationEnabled) + jpaQueryFactory.select(member.notificationEnabled) .from(member) .where(member.id.eq(memberId)) .fetchOne() @@ -174,7 +175,7 @@ public Optional findIsAlarmByMemberId(final Long memberId) { @Override public List findMemberIdsByIds(final List ids) { - return query + return jpaQueryFactory .select(member.id) .from(member) .where(member.id.in(ids)) @@ -183,7 +184,7 @@ public List findMemberIdsByIds(final List ids) { @Override public boolean checkTagDuplication(final String tag) { - final Integer count = query.selectOne() + final Integer count = jpaQueryFactory.selectOne() .from(member) .where(member.tag.eq(tag)) .fetchFirst(); @@ -192,11 +193,19 @@ public boolean checkTagDuplication(final String tag) { } @Override - public Optional findMemberPhoneHash(final Long memberId) { - return Optional.ofNullable(query.select(member.phone_hash) + public Optional findMemberPhoneHash(final Long memberId) { + final byte[] phoneHash = jpaQueryFactory + .select( + member.phone_hash + ) .from(member) .where(member.id.eq(memberId)) - .fetchOne() - ); + .fetchOne(); + + if (phoneHash != null) { + return Optional.of(new ByteArrayWrapper(phoneHash)); + } + + return Optional.empty(); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/common/wrapper/ByteArrayWrapper.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/common/wrapper/ByteArrayWrapper.java index 2385b0805..643d90454 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/common/wrapper/ByteArrayWrapper.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/common/wrapper/ByteArrayWrapper.java @@ -2,19 +2,12 @@ import java.util.Arrays; -public final class ByteArrayWrapper { +public record ByteArrayWrapper(byte[] data) { - private final byte[] data; - - public ByteArrayWrapper(final byte[] data) { + public ByteArrayWrapper { if (data == null) { throw new NullPointerException(); } - this.data = data; - } - - public byte[] getData() { - return data; } @Override diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java index ccac4f31c..c9a347d04 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java @@ -61,6 +61,10 @@ public static Member member(int dataPrefix) { return member; } + public static ByteArrayWrapper getPhoneByteWrapper(int dataPrefix) { + return new ByteArrayWrapper(getPhoneBytes(dataPrefix)); + } + /** * 테스트 픽스처 - 멤버마다 상이한 번호를 위해 dataPrefix를 주면 해당 dataPrefix에 대한 핸드폰 번호 바이트를 반환한다. *
주의 - 테스트에서 같은 prefix를 사용하면 오류가 발생하므로 서로 다른 prefix를 쓰도록 해야함. diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java index 0339da230..8a43a195e 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/friend/service/query/FriendQueryServiceTest.java @@ -59,7 +59,7 @@ class FriendQueryServiceTest { List phones = MemberFixture.getPhones(5); given(memberRepository.findMemberPhoneHash(anyLong())).willReturn( - Optional.of(MemberFixture.getPhoneBytes(startFriendsIndex))); + Optional.of(MemberFixture.getPhoneByteWrapper(startFriendsIndex))); given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) .willReturn(FriendDtoFixture.getFriendSummaryDtos(5)); @@ -79,7 +79,7 @@ class FriendQueryServiceTest { List phones = Collections.emptyList(); given(memberRepository.findMemberPhoneHash(anyLong())).willReturn( - Optional.of(MemberFixture.getPhoneBytes(startFriendsIndex))); + Optional.of(MemberFixture.getPhoneByteWrapper(startFriendsIndex))); given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) .willReturn(Collections.emptyList()); @@ -101,7 +101,7 @@ class FriendQueryServiceTest { List phones = MemberFixture.getPhones(friendsCount); given(memberRepository.findMemberPhoneHash(anyLong())).willReturn( - Optional.of(MemberFixture.getPhoneBytes(startFriendsIndex))); + Optional.of(MemberFixture.getPhoneByteWrapper(startFriendsIndex))); given(memberFriendRepository.findFriendsByPhone(anyLong(), anyList())) .willReturn(FriendDtoFixture.getFriendSummaryDtos(friendsCount)); From 822edd5a339a9a5b201c9c4c1f9f189fb9f2e303 Mon Sep 17 00:00:00 2001 From: GaBaljaintheroom Date: Mon, 24 Jun 2024 00:07:03 +0900 Subject: [PATCH 032/157] =?UTF-8?q?fix=20:=20request=20Valid=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/data/reqeust/UpdateMemberDataRequest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java index 3ab7571be..4814aff7d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/reqeust/UpdateMemberDataRequest.java @@ -1,15 +1,18 @@ package site.timecapsulearchive.core.domain.member.data.reqeust; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import site.timecapsulearchive.core.domain.member.data.dto.UpdateMemberDataDto; @Schema(description = "회원 업데이트 포맷") public record UpdateMemberDataRequest( @Schema(description = "닉네임") + @NotNull(message = "사용자 닉네임은 필수 입니다.") String nickname, @Schema(description = "태그") + @NotNull(message = "사용자 태그는 필수 입니다.") String tag ) { From 88b4181dfb134c0812b4a35c7e7d44e70fb6e2bf Mon Sep 17 00:00:00 2001 From: hong seokho Date: Sun, 23 Jun 2024 15:44:22 +0900 Subject: [PATCH 033/157] =?UTF-8?q?fix=20:=20is=5Fdelete=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/capsule/entity/Capsule.java | 9 +++++- .../capsule/entity/GroupCapsuleOpen.java | 11 +++++-- .../core/domain/capsule/entity/Image.java | 9 +++++- .../core/domain/capsule/entity/Video.java | 9 +++++- .../image/ImageQueryRepositoryImpl.java | 5 +-- .../video/VideoQueryRepositoryImpl.java | 5 +-- .../GroupCapsuleOpenQueryRepository.java | 5 +-- .../capsuleskin/entity/CapsuleSkin.java | 9 +++++- .../domain/friend/entity/FriendInvite.java | 9 +++++- .../domain/friend/entity/MemberFriend.java | 9 +++++- .../FriendInviteQueryRepositoryImpl.java | 5 +-- .../core/domain/group/entity/Group.java | 9 +++++- .../core/domain/history/entity/History.java | 9 +++++- .../domain/history/entity/HistoryImage.java | 9 +++++- .../core/domain/member/entity/Member.java | 9 +++++- .../domain/member/entity/MemberTemporary.java | 9 +++++- .../domain/member/entity/Notification.java | 9 +++++- .../member/entity/NotificationCategory.java | 9 +++++- .../member_group/entity/GroupInvite.java | 9 +++++- .../member_group/entity/MemberGroup.java | 12 ++++++- .../GroupInviteQueryRepositoryImpl.java | 5 +-- .../db/migration/V30__is_deleted.sql | 31 +++++++++++++++++++ 22 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 backend/core/src/main/resources/db/migration/V30__is_deleted.sql diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java index bb55358ec..59681f3c4 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Capsule.java @@ -21,6 +21,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; import org.locationtech.jts.geom.Point; import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; @@ -30,9 +32,11 @@ import site.timecapsulearchive.core.global.entity.BaseEntity; @Entity +@Table(name = "capsule") @Getter +@SQLDelete(sql = "UPDATE `capsule` SET is_deleted = true WHERE capsule_id = ?") +@Where(clause = "is_deleted = false") @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "CAPSULE") public class Capsule extends BaseEntity { @Id @@ -59,6 +63,9 @@ public class Capsule extends BaseEntity { @Column(name = "is_opened", nullable = false) private Boolean isOpened; + @Column(name = "is_deleted") + private boolean is_deleted = Boolean.FALSE; + @Embedded private Address address; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java index 1364a6fc8..e9e92d69c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java @@ -12,14 +12,18 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.entity.BaseEntity; -@Entity @Getter +@Table(name = "group_capsule_open") +@Entity +@SQLDelete(sql = "UPDATE `group_capsule_open` SET is_deleted = true WHERE group_capsule_open_id = ?") +@Where(clause = "is_deleted = false") @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "GROUP_CAPSULE_OPEN") public class GroupCapsuleOpen extends BaseEntity { @Id @@ -30,6 +34,9 @@ public class GroupCapsuleOpen extends BaseEntity { @Column(name = "is_opened", nullable = false) private Boolean isOpened; + @Column(name = "is_deleted") + private boolean is_deleted = Boolean.FALSE; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "capsule_id", nullable = false) private Capsule capsule; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Image.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Image.java index 9c4a11ac4..e6ed1e383 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Image.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Image.java @@ -14,13 +14,17 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.entity.BaseEntity; @Entity +@Table(name = "image") @Getter +@SQLDelete(sql = "UPDATE `image` SET is_deleted = true WHERE image_id = ?") +@Where(clause = "is_deleted = false") @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "IMAGE") public class Image extends BaseEntity { @Id @@ -31,6 +35,9 @@ public class Image extends BaseEntity { @Column(name = "image_url", nullable = false) private String imageUrl; + @Column(name = "is_deleted") + private boolean is_deleted = Boolean.FALSE; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "capsule_id", nullable = false) private Capsule capsule; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Video.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Video.java index 3e55b249b..6dbd7fa35 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Video.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/Video.java @@ -14,13 +14,17 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.entity.BaseEntity; @Entity +@Table(name = "video") @Getter +@SQLDelete(sql = "UPDATE `video` SET is_deleted = true WHERE video_id = ?") +@Where(clause = "is_deleted = false") @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "VIDEO") public class Video extends BaseEntity { @Id @@ -31,6 +35,9 @@ public class Video extends BaseEntity { @Column(name = "video_url", nullable = false) private String videoUrl; + @Column(name = "is_deleted") + private boolean is_deleted = Boolean.FALSE; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "capsule_id", nullable = false) private Capsule capsule; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/image/ImageQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/image/ImageQueryRepositoryImpl.java index ccd86258d..445d08ae9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/image/ImageQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/image/ImageQueryRepositoryImpl.java @@ -22,8 +22,8 @@ public void bulkSave(final List images) { jdbcTemplate.batchUpdate( """ INSERT INTO image ( - image_id, image_url, member_id, capsule_id, created_at, updated_at - ) values (?, ?, ?, ?, ?, ?) + image_id, image_url, member_id, capsule_id, created_at, updated_at, is_deleted + ) values (?, ?, ?, ?, ?, ?, ?) """, new BatchPreparedStatementSetter() { @@ -36,6 +36,7 @@ public void setValues(final PreparedStatement ps, final int i) throws SQLExcepti ps.setLong(4, image.getCapsule().getId()); ps.setTimestamp(5, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); ps.setTimestamp(6, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setBoolean(7, Boolean.FALSE); } @Override diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/video/VideoQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/video/VideoQueryRepositoryImpl.java index 2c3597749..929b674ec 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/video/VideoQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/generic_capsule/repository/video/VideoQueryRepositoryImpl.java @@ -22,8 +22,8 @@ public void bulkSave(final List