From 98c5138622b4861a8ba3306bde331a8902b5f25b Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 00:58:31 +0900 Subject: [PATCH 01/24] =?UTF-8?q?[feat]=20:=20RestTemplateConfig=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/RestTemplateConfig.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java diff --git a/src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java b/src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java new file mode 100644 index 00000000..07fddbf7 --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java @@ -0,0 +1,21 @@ +package com.dnd.gongmuin.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate() { + RestTemplate restTemplate = new RestTemplate(); + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setConnectTimeout(5000); + requestFactory.setReadTimeout(5000); + + restTemplate.setRequestFactory(requestFactory); + + return restTemplate; + } +} From 16c1b2327ec7a74d42aa41b8a3dfe8970e4153b3 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 00:59:22 +0900 Subject: [PATCH 02/24] =?UTF-8?q?[feat]=20:=20OAuth2AccessToken=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/security/oauth2/KakaoResponse.java | 9 ++++++++- .../com/dnd/gongmuin/security/oauth2/NaverResponse.java | 9 ++++++++- .../com/dnd/gongmuin/security/oauth2/Oauth2Response.java | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java b/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java index 4cf40fea..ba082855 100644 --- a/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java +++ b/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java @@ -8,10 +8,12 @@ public class KakaoResponse implements Oauth2Response { private final Map attribute; private final Long id; + private final String oauth2AccessToken; - public KakaoResponse(Map attribute) { + public KakaoResponse(Map attribute, String oauth2AccessToken) { this.attribute = (Map)attribute.get("kakao_account"); this.id = (Long)attribute.get("id"); + this.oauth2AccessToken = oauth2AccessToken; } @Override @@ -43,4 +45,9 @@ public String createSocialEmail() { ); } + @Override + public String getOauth2AccessToken() { + return this.oauth2AccessToken; + } + } diff --git a/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java b/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java index 79b7df2f..5437833e 100644 --- a/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java +++ b/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java @@ -7,9 +7,11 @@ public class NaverResponse implements Oauth2Response { private final Map attribute; + private final String oauth2AccessToken; - public NaverResponse(Map attribute) { + public NaverResponse(Map attribute, String oauth2AccessToken) { this.attribute = (Map)attribute.get("response"); + this.oauth2AccessToken = oauth2AccessToken; } @Override @@ -40,4 +42,9 @@ public String createSocialEmail() { this.getEmail() ); } + + @Override + public String getOauth2AccessToken() { + return this.oauth2AccessToken; + } } diff --git a/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java b/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java index 745828d5..3be2eafb 100644 --- a/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java +++ b/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java @@ -11,4 +11,5 @@ public interface Oauth2Response { String createSocialEmail(); + String getOauth2AccessToken(); } From 7bcd7f96411010fc5001c2c21d922b9b87af7c48 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:00:38 +0900 Subject: [PATCH 03/24] =?UTF-8?q?[feat]=20:=20oauth2AccessToken=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberService.java | 25 ++++++++++++++++++- .../service/CustomOauth2UserService.java | 7 ++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java index 82dd5075..ab3afe3f 100644 --- a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java +++ b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java @@ -1,5 +1,7 @@ package com.dnd.gongmuin.member.service; +import java.time.Duration; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -23,6 +25,7 @@ import com.dnd.gongmuin.member.dto.response.QuestionPostsResponse; import com.dnd.gongmuin.member.exception.MemberErrorCode; import com.dnd.gongmuin.member.repository.MemberRepository; +import com.dnd.gongmuin.redis.util.RedisUtil; import com.dnd.gongmuin.security.oauth2.Oauth2Response; import lombok.RequiredArgsConstructor; @@ -31,12 +34,16 @@ @RequiredArgsConstructor public class MemberService { + private static final long ACCESS_TOKEN_EXPIRATION = 3600 * 1000; private final MemberRepository memberRepository; + private final RedisUtil redisUtil; public Member saveOrUpdate(Oauth2Response oauth2Response) { Member member = memberRepository.findBySocialEmail(oauth2Response.createSocialEmail()) .map(m -> { m.updateSocialEmail(oauth2Response.createSocialEmail()); + deleteExistingOauthAccessToken(m); + saveOauth2AccessToken(oauth2Response, m); return m; }) .orElseGet(() -> createMemberFromOauth2Response(oauth2Response)); @@ -44,13 +51,29 @@ public Member saveOrUpdate(Oauth2Response oauth2Response) { return memberRepository.save(member); } + private void saveOauth2AccessToken(Oauth2Response oauth2Response, Member m) { + redisUtil.setValues( + "AT(oauth):" + m.getSocialEmail(), + oauth2Response.getOauth2AccessToken(), + Duration.ofMillis(ACCESS_TOKEN_EXPIRATION) + ); + } + + private void deleteExistingOauthAccessToken(Member m) { + if (redisUtil.getValues("AT(oauth2):" + m.getSocialEmail()) != null) { + redisUtil.deleteValues("AT(oauth2):" + m.getSocialEmail()); + } + } + public Provider parseProviderFromSocialEmail(Member member) { String socialEmail = member.getSocialEmail(); return Provider.fromSocialEmail(socialEmail); } private Member createMemberFromOauth2Response(Oauth2Response oauth2Response) { - return Member.of(oauth2Response.getName(), oauth2Response.createSocialEmail(), 10000, "ROLE_GUEST"); + Member member = Member.of(oauth2Response.getName(), oauth2Response.createSocialEmail(), 10000, "ROLE_GUEST"); + saveOauth2AccessToken(oauth2Response, member); + return member; } public Member getMemberBySocialEmail(String socialEmail) { diff --git a/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java b/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java index c370c2d5..9c907c35 100644 --- a/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java +++ b/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java @@ -34,13 +34,15 @@ public class CustomOauth2UserService extends DefaultOAuth2UserService { public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); + String oauth2AccessToken = userRequest.getAccessToken().getTokenValue(); + String registrationId = userRequest.getClientRegistration().getRegistrationId(); Oauth2Response oauth2Response = null; if (Objects.equals(registrationId, KAKAO.getLabel())) { - oauth2Response = new KakaoResponse(oAuth2User.getAttributes()); + oauth2Response = new KakaoResponse(oAuth2User.getAttributes(), oauth2AccessToken); } else if (Objects.equals(registrationId, NAVER.getLabel())) { - oauth2Response = new NaverResponse(oAuth2User.getAttributes()); + oauth2Response = new NaverResponse(oAuth2User.getAttributes(), oauth2AccessToken); } else { throw new OAuth2AuthenticationException( new OAuth2Error(UNSUPPORTED_SOCIAL_LOGIN.getCode()), @@ -57,5 +59,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic ); return new CustomOauth2User(authInfo); } + } From c1cd7cf0b4c09019e170b1fc96b43b72c630282e Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:01:54 +0900 Subject: [PATCH 04/24] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/gongmuin/auth/controller/AuthController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java index 5a13c537..1ae2e0c9 100644 --- a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java +++ b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java @@ -12,6 +12,7 @@ import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; +import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -93,5 +94,16 @@ public ResponseEntity reissue(HttpServletRequest request, HttpS return ResponseEntity.ok(reissueResponse); } + + @Operation(summary = "회원탈퇴 API", description = "회원 탈퇴한다.") + @ApiResponse(useReturnTypeSchema = true) + @PostMapping("/delete") + public ResponseEntity deleteMember( + HttpServletRequest request + ) { + MemberDeletionResponse memberDeletionResponse = authService.deleteMember(request); + + return ResponseEntity.ok(memberDeletionResponse); + } } From 295c46aa12dff1252dd6629aaef2a9f62190f865 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:02:06 +0900 Subject: [PATCH 05/24] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=9D=91=EB=8B=B5=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gongmuin/auth/dto/response/MemberDeletionResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java diff --git a/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java b/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java new file mode 100644 index 00000000..b14e58c7 --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java @@ -0,0 +1,7 @@ +package com.dnd.gongmuin.auth.dto.response; + +public record MemberDeletionResponse( + + Long memberId +) { +} From e2b0500b77b78b7e07472ead159273ec795e01da Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:02:18 +0900 Subject: [PATCH 06/24] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=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 --- .../com/dnd/gongmuin/member/exception/MemberErrorCode.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java b/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java index a8d07ea2..eeab7457 100644 --- a/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java +++ b/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java @@ -16,7 +16,8 @@ public enum MemberErrorCode implements ErrorCode { UPDATE_PROFILE_FAILED("프로필 수정에 실패했습니다.", "MEMBER_005"), QUESTION_POSTS_BY_MEMBER_FAILED("마이페이지 게시글 목록을 불러오는데 실패했습니다", "MEMBER_006"), NOT_FOUND_JOB_GROUP("직군을 올바르게 입력해주세요.", "MEMBER_007"), - NOT_FOUND_JOB_CATEGORY("직렬을 올바르게 입력해주세요.", "MEMBER_008"); + NOT_FOUND_JOB_CATEGORY("직렬을 올바르게 입력해주세요.", "MEMBER_008"), + DELETE_FAILED("회원탈퇴를 실패했습니다.", "MEMBER_009"); private final String message; private final String code; From 5b5c3376f50414eecdea67eff78283fc812ebe93 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:02:38 +0900 Subject: [PATCH 07/24] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gongmuin/auth/service/AuthService.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java index be7491c3..49bffd11 100644 --- a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java +++ b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java @@ -13,6 +13,7 @@ import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; +import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -30,6 +31,7 @@ import com.dnd.gongmuin.security.jwt.util.TokenProvider; import com.dnd.gongmuin.security.oauth2.AuthInfo; import com.dnd.gongmuin.security.oauth2.CustomOauth2User; +import com.dnd.gongmuin.security.service.OAuth2UnlinkService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -40,10 +42,12 @@ public class AuthService { private static final String LOGOUT = "logout"; + private static final String DELETE = "delete"; private final TokenProvider tokenProvider; private final MemberRepository memberRepository; private final CookieUtil cookieUtil; private final RedisUtil redisUtil; + private final OAuth2UnlinkService oAuth2UnlinkService; @Transactional public TempSignResponse tempSignUp(TempSignUpRequest tempSignUpRequest, HttpServletResponse response) { @@ -181,4 +185,41 @@ private void updateAdditionalInfo(AdditionalInfoRequest request, Member findMemb ); } + @Transactional + public MemberDeletionResponse deleteMember(HttpServletRequest request) { + String accessToken = cookieUtil.getCookieValue(request); + + if (!tokenProvider.validateToken(accessToken, new Date())) { + throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); + } + + Authentication authentication = tokenProvider.getAuthentication(accessToken); + Member member = (Member)authentication.getPrincipal(); + + // RefreshToken 삭제 + if (!Objects.isNull(redisUtil.getValues("RT:" + member.getSocialEmail()))) { + redisUtil.deleteValues("RT:" + member.getSocialEmail()); + } + + // 현재 발급 되어 있는 AccessToken 블랙리스트 등록 + Long expiration = tokenProvider.getExpiration(accessToken, new Date()); + redisUtil.setValues(accessToken, DELETE, Duration.ofMillis(expiration)); + + // AccessToken 블랙리스트 등록 여부 검증 + String values = redisUtil.getValues(accessToken); + if (!Objects.equals(values, DELETE)) { + throw new NotFoundException(MemberErrorCode.DELETE_FAILED); + } + + // TODO: SOFT DELETE + + // oauth2 서비스 연결 끊기 + oAuth2UnlinkService.unlink(member.getSocialEmail()); + + // oauth2 access 토큰 삭제 + if (redisUtil.getValues("AT(oauth):" + member.getSocialEmail()) != null) { + redisUtil.deleteValues("AT(oauth):" + member.getSocialEmail()); + } + return new MemberDeletionResponse(member.getId()); + } } From c949f46d85bcf3fcff0dcd996e746198e181ba6c Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:03:08 +0900 Subject: [PATCH 08/24] =?UTF-8?q?[feat]=20:=20Oauth2=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EB=81=8A=EA=B8=B0=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20Service=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/OAuth2UnlinkService.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java diff --git a/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java b/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java new file mode 100644 index 00000000..8b03720c --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java @@ -0,0 +1,105 @@ +package com.dnd.gongmuin.security.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import com.dnd.gongmuin.common.exception.runtime.ValidationException; +import com.dnd.gongmuin.redis.util.RedisUtil; +import com.dnd.gongmuin.security.exception.OAuth2ErrorCode; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class OAuth2UnlinkService { + + private static final String KAKAO_URL = "https://kapi.kakao.com/v1/user/unlink"; + private static final String NAVER_URL = "https://nid.naver.com/oauth2.0/token"; + private final RestTemplate restTemplate; + private final RedisUtil redisUtil; + @Value("${spring.security.oauth2.client.registration.naver.client-id}") + private String NAVER_CLIENT_ID; + @Value("${spring.security.oauth2.client.registration.naver.client-secret}") + private String NAVER_CLIENT_SECRET; + + public void unlink(String provider) { + if (provider.startsWith("kakao")) { + kakaoUnlink(provider); + } else if (provider.startsWith("naver")) { + naverUnlink(provider); + } else { + throw new ValidationException(OAuth2ErrorCode.INVALID_REQUEST); + } + } + + public void kakaoUnlink(String provider) { + String accessToken = redisUtil.getValues("AT(oauth):" + provider); + // oauth2 토큰이 만료 시 재 로그인 + if (accessToken == null) { + throw new ValidationException(OAuth2ErrorCode.EXPIRED_AUTH_TOKEN); + } + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + HttpEntity entity = new HttpEntity<>("", headers); + ResponseEntity responseEntity = restTemplate.exchange( + KAKAO_URL, + HttpMethod.POST, + entity, + String.class + ); + + if (responseEntity.getBody().isEmpty()) { + throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + public void naverUnlink(String provider) { + String accessToken = redisUtil.getValues("AT(oauth):" + provider); + + // oauth2 토큰이 만료 시 재 로그인 + if (accessToken == null) { + throw new ValidationException(OAuth2ErrorCode.EXPIRED_AUTH_TOKEN); + } + + String url = NAVER_URL + + "?service_provider=NAVER" + + "&grant_type=delete" + + "&client_id=" + + NAVER_CLIENT_ID + + "&client_secret=" + + NAVER_CLIENT_SECRET + + "&access_token=" + + accessToken; + + NaverUnlinkResponse response = restTemplate.getForObject(url, NaverUnlinkResponse.class); + + if (response != null && !"success".equalsIgnoreCase(response.getResult())) { + throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + /** + * 네이버 응답 데이터 + */ + @Getter + public static class NaverUnlinkResponse { + private final String accessToken; + private final String result; + + @JsonCreator + public NaverUnlinkResponse( + @JsonProperty("access_token") String accessToken, + @JsonProperty("result") String result) { + this.accessToken = accessToken; + this.result = result; + } + } +} From 191cb3090b43ab282c01b3b7575531a31f8e466f Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:03:28 +0900 Subject: [PATCH 09/24] =?UTF-8?q?[feat]=20:=20OAuth2=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=EC=BD=94=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 --- .../security/exception/OAuth2ErrorCode.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java diff --git a/src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java b/src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java new file mode 100644 index 00000000..717274ee --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java @@ -0,0 +1,17 @@ +package com.dnd.gongmuin.security.exception; + +import com.dnd.gongmuin.common.exception.ErrorCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum OAuth2ErrorCode implements ErrorCode { + + INVALID_REQUEST("유효하지 않은 탈퇴 요청입니다.", "OAUTH2_001"), + EXPIRED_AUTH_TOKEN("만료된 OAuth2 토큰입니다.", "OAUTH2_002"), + INTERNAL_SERVER_ERROR("OAuth 서버 에러 발생입니다.", "OAUTH2_003"); + private final String message; + private final String code; +} From 75c67f654153fd956040ff58390f6c71a68c2de6 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:04:00 +0900 Subject: [PATCH 10/24] =?UTF-8?q?[refactor}=20:=20=EB=B8=94=EB=9E=99?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/jwt/util/TokenAuthenticationFilter.java | 2 +- .../com/dnd/gongmuin/security/jwt/util/TokenProvider.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java index eea059b5..464ab581 100644 --- a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java +++ b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java @@ -33,7 +33,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (tokenProvider.validateToken(accessToken, new Date())) { // accessToken logout 여부 확인 - if (tokenProvider.verifyLogout(accessToken)) { + if (tokenProvider.verifyBlackList(accessToken)) { saveAuthentication(accessToken); } } diff --git a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java index 8bdb8966..fd4e25bc 100644 --- a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java +++ b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java @@ -1,10 +1,10 @@ package com.dnd.gongmuin.security.jwt.util; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import javax.crypto.SecretKey; @@ -40,6 +40,7 @@ public class TokenProvider { private static final String ROLE_KEY = "ROLE"; + private static final String[] BLACKLIST = new String[] {"false", "delete"}; private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 90L; private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24L; private final MemberRepository memberRepository; @@ -136,9 +137,9 @@ public Long getExpiration(String token, Date date) { return (expiration.getTime() - date.getTime()); } - public boolean verifyLogout(String accessToken) { + public boolean verifyBlackList(String accessToken) { String value = redisUtil.getValues(accessToken); - return Objects.equals("false", value); + return Arrays.asList(BLACKLIST).contains(value); } } From dee41ab9d9c76febd13fc0618e0f9f3bdc85aa1e Mon Sep 17 00:00:00 2001 From: dudxo Date: Fri, 27 Sep 2024 16:53:53 +0900 Subject: [PATCH 11/24] =?UTF-8?q?[rename]=20:=20DTO=20=EC=BB=A8=EB=B2=A4?= =?UTF-8?q?=EC=85=98=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/auth/controller/AuthController.java | 8 ++++---- ...berDeletionResponse.java => DeleteMemberResponse.java} | 2 +- .../java/com/dnd/gongmuin/auth/service/AuthService.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/com/dnd/gongmuin/auth/dto/response/{MemberDeletionResponse.java => DeleteMemberResponse.java} (63%) diff --git a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java index 1ae2e0c9..0c11db55 100644 --- a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java +++ b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java @@ -11,8 +11,8 @@ import com.dnd.gongmuin.auth.dto.request.TempSignInRequest; import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; +import com.dnd.gongmuin.auth.dto.response.DeleteMemberResponse; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; -import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -98,12 +98,12 @@ public ResponseEntity reissue(HttpServletRequest request, HttpS @Operation(summary = "회원탈퇴 API", description = "회원 탈퇴한다.") @ApiResponse(useReturnTypeSchema = true) @PostMapping("/delete") - public ResponseEntity deleteMember( + public ResponseEntity deleteMember( HttpServletRequest request ) { - MemberDeletionResponse memberDeletionResponse = authService.deleteMember(request); + DeleteMemberResponse deleteMemberResponse = authService.deleteMember(request); - return ResponseEntity.ok(memberDeletionResponse); + return ResponseEntity.ok(deleteMemberResponse); } } diff --git a/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java b/src/main/java/com/dnd/gongmuin/auth/dto/response/DeleteMemberResponse.java similarity index 63% rename from src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java rename to src/main/java/com/dnd/gongmuin/auth/dto/response/DeleteMemberResponse.java index b14e58c7..1a661cf5 100644 --- a/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java +++ b/src/main/java/com/dnd/gongmuin/auth/dto/response/DeleteMemberResponse.java @@ -1,6 +1,6 @@ package com.dnd.gongmuin.auth.dto.response; -public record MemberDeletionResponse( +public record DeleteMemberResponse( Long memberId ) { diff --git a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java index 49bffd11..6a802d1f 100644 --- a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java +++ b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java @@ -12,8 +12,8 @@ import com.dnd.gongmuin.auth.dto.request.TempSignInRequest; import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; +import com.dnd.gongmuin.auth.dto.response.DeleteMemberResponse; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; -import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -186,7 +186,7 @@ private void updateAdditionalInfo(AdditionalInfoRequest request, Member findMemb } @Transactional - public MemberDeletionResponse deleteMember(HttpServletRequest request) { + public DeleteMemberResponse deleteMember(HttpServletRequest request) { String accessToken = cookieUtil.getCookieValue(request); if (!tokenProvider.validateToken(accessToken, new Date())) { @@ -220,6 +220,6 @@ public MemberDeletionResponse deleteMember(HttpServletRequest request) { if (redisUtil.getValues("AT(oauth):" + member.getSocialEmail()) != null) { redisUtil.deleteValues("AT(oauth):" + member.getSocialEmail()); } - return new MemberDeletionResponse(member.getId()); + return new DeleteMemberResponse(member.getId()); } } From f0c373445accc6ad91831ef76b5c02619f554820 Mon Sep 17 00:00:00 2001 From: dudxo Date: Fri, 27 Sep 2024 20:01:04 +0900 Subject: [PATCH 12/24] =?UTF-8?q?[rename]=20:=20=EA=B8=B0=EC=A1=B4=20OAuth?= =?UTF-8?q?AccessToken=20=EC=A1=B4=EC=9E=AC=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=ED=9B=84=20=EC=82=AD=EC=A0=9C=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dnd/gongmuin/member/service/MemberService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java index ab3afe3f..dfa39adb 100644 --- a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java +++ b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java @@ -42,7 +42,7 @@ public Member saveOrUpdate(Oauth2Response oauth2Response) { Member member = memberRepository.findBySocialEmail(oauth2Response.createSocialEmail()) .map(m -> { m.updateSocialEmail(oauth2Response.createSocialEmail()); - deleteExistingOauthAccessToken(m); + deleteOauthAccessTokenIfExists(m); saveOauth2AccessToken(oauth2Response, m); return m; }) @@ -59,7 +59,7 @@ private void saveOauth2AccessToken(Oauth2Response oauth2Response, Member m) { ); } - private void deleteExistingOauthAccessToken(Member m) { + private void deleteOauthAccessTokenIfExists(Member m) { if (redisUtil.getValues("AT(oauth2):" + m.getSocialEmail()) != null) { redisUtil.deleteValues("AT(oauth2):" + m.getSocialEmail()); } From c6a344876d75b4ed15916e997a0e5de76354a6af Mon Sep 17 00:00:00 2001 From: dudxo Date: Tue, 22 Oct 2024 22:29:12 +0900 Subject: [PATCH 13/24] =?UTF-8?q?[test]=20:=20MemberFixture=20socialEmail?= =?UTF-8?q?=20Provider=20=EB=8C=80=EB=AC=B8=EC=9E=90=20->=20=EC=86=8C?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/common/fixture/MemberFixture.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/dnd/gongmuin/common/fixture/MemberFixture.java b/src/test/java/com/dnd/gongmuin/common/fixture/MemberFixture.java index bf8dad0f..ea266874 100644 --- a/src/test/java/com/dnd/gongmuin/common/fixture/MemberFixture.java +++ b/src/test/java/com/dnd/gongmuin/common/fixture/MemberFixture.java @@ -20,7 +20,7 @@ public static Member member() { "회원123", JobGroup.ENG, JobCategory.ME, - "KAKAO123/gongmuin@daum.net", + "kakao123/gongmuin@daum.net", "gongmuin@korea.kr", 10000, "ROLE_USER" @@ -33,7 +33,7 @@ public static Member member2() { "소셜회원", JobGroup.ENG, JobCategory.ME, - "KAKAO123/member2@daum.net", + "kakao123/member2@daum.net", "member2@korea.kr", 20000, "ROLE_USER" @@ -43,7 +43,7 @@ public static Member member2() { public static Member member3() { return Member.of( "소셜회원", - "KAKAO123/member2@daum.net", + "kakao123/member2@daum.net", 20000, "ROLE_GUEST" ); @@ -55,7 +55,7 @@ public static Member member4() { "소셜회원", JobGroup.AD, JobCategory.ME, - "KAKAO1234/member2@daum.net", + "kakao1234/member2@daum.net", "member@korea.kr", 20000, "ROLE_USER" @@ -68,7 +68,7 @@ public static Member member(Long memberId) { "회원123", JobGroup.ENG, JobCategory.ME, - "KAKAO123/gongmuin@daum.net", + "kakao123/gongmuin@daum.net", "gongmuin@korea.kr", 10000, "ROLE_USER" From fffda197f301726ba23d40a40855e057af4532f3 Mon Sep 17 00:00:00 2001 From: dudxo Date: Tue, 22 Oct 2024 23:01:27 +0900 Subject: [PATCH 14/24] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EC=8B=9C=20Hard=5Fdelete=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gongmuin/auth/service/AuthService.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java index 6a802d1f..2792fa80 100644 --- a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java +++ b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java @@ -2,12 +2,15 @@ import java.time.Duration; import java.util.Date; +import java.util.List; import java.util.Objects; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.dnd.gongmuin.answer.domain.Answer; +import com.dnd.gongmuin.answer.repository.AnswerRepository; import com.dnd.gongmuin.auth.dto.request.AdditionalInfoRequest; import com.dnd.gongmuin.auth.dto.request.TempSignInRequest; import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; @@ -21,11 +24,16 @@ import com.dnd.gongmuin.auth.exception.AuthErrorCode; import com.dnd.gongmuin.common.exception.runtime.NotFoundException; import com.dnd.gongmuin.common.exception.runtime.ValidationException; +import com.dnd.gongmuin.credit_history.repository.CreditHistoryRepository; import com.dnd.gongmuin.member.domain.JobCategory; import com.dnd.gongmuin.member.domain.JobGroup; import com.dnd.gongmuin.member.domain.Member; import com.dnd.gongmuin.member.exception.MemberErrorCode; import com.dnd.gongmuin.member.repository.MemberRepository; +import com.dnd.gongmuin.notification.repository.NotificationRepository; +import com.dnd.gongmuin.post_interaction.repository.InteractionRepository; +import com.dnd.gongmuin.question_post.domain.QuestionPost; +import com.dnd.gongmuin.question_post.repository.QuestionPostRepository; import com.dnd.gongmuin.redis.util.RedisUtil; import com.dnd.gongmuin.security.jwt.util.CookieUtil; import com.dnd.gongmuin.security.jwt.util.TokenProvider; @@ -43,11 +51,17 @@ public class AuthService { private static final String LOGOUT = "logout"; private static final String DELETE = "delete"; + private static final String ANONYMOUS = "ROLE_ANONYMOUS"; private final TokenProvider tokenProvider; private final MemberRepository memberRepository; private final CookieUtil cookieUtil; private final RedisUtil redisUtil; private final OAuth2UnlinkService oAuth2UnlinkService; + private final CreditHistoryRepository creditHistoryRepository; + private final QuestionPostRepository questionPostRepository; + private final AnswerRepository answerRepository; + private final InteractionRepository interactionRepository; + private final NotificationRepository notificationRepository; @Transactional public TempSignResponse tempSignUp(TempSignUpRequest tempSignUpRequest, HttpServletResponse response) { @@ -211,7 +225,9 @@ public DeleteMemberResponse deleteMember(HttpServletRequest request) { throw new NotFoundException(MemberErrorCode.DELETE_FAILED); } - // TODO: SOFT DELETE + deleteAssociation(member); + replaceWithAnonymous(member); + memberRepository.delete(member); // oauth2 서비스 연결 끊기 oAuth2UnlinkService.unlink(member.getSocialEmail()); @@ -222,4 +238,23 @@ public DeleteMemberResponse deleteMember(HttpServletRequest request) { } return new DeleteMemberResponse(member.getId()); } + + private void deleteAssociation(Member member) { + creditHistoryRepository.deleteByMember(member); + notificationRepository.deleteByMember(member); + interactionRepository.deleteByMemberId(member.getId()); + } + + private void replaceWithAnonymous(Member member) { + Member anonymous = memberRepository.findByRole(ANONYMOUS) + .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); + + List posts = questionPostRepository.findAllByMember(member); + posts.forEach(post -> post.updateMember(anonymous)); + + List answers = answerRepository.findAllByMember(member); + answers.forEach(answer -> answer.updateMember(anonymous)); + + } + } From ee8a1b7c7a40a03b2366b90ec2835aa1bb5f1c0d Mon Sep 17 00:00:00 2001 From: dudxo Date: Tue, 22 Oct 2024 23:03:12 +0900 Subject: [PATCH 15/24] =?UTF-8?q?[feat]=20:=20=EC=97=B0=EA=B4=80=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EB=B3=80=EA=B2=BD=20=EB=A9=94=EC=84=9C?= =?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 --- src/main/java/com/dnd/gongmuin/answer/domain/Answer.java | 3 +++ .../com/dnd/gongmuin/question_post/domain/QuestionPost.java | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/com/dnd/gongmuin/answer/domain/Answer.java b/src/main/java/com/dnd/gongmuin/answer/domain/Answer.java index 940ce7da..e8db2b3e 100644 --- a/src/main/java/com/dnd/gongmuin/answer/domain/Answer.java +++ b/src/main/java/com/dnd/gongmuin/answer/domain/Answer.java @@ -61,4 +61,7 @@ public void updateIsChosen() { this.isChosen = true; } + public void updateMember(Member anonymous) { + this.member = anonymous; + } } diff --git a/src/main/java/com/dnd/gongmuin/question_post/domain/QuestionPost.java b/src/main/java/com/dnd/gongmuin/question_post/domain/QuestionPost.java index 241633c1..8f2373a1 100644 --- a/src/main/java/com/dnd/gongmuin/question_post/domain/QuestionPost.java +++ b/src/main/java/com/dnd/gongmuin/question_post/domain/QuestionPost.java @@ -122,4 +122,8 @@ public void updateQuestionPost( this.reward = reward; this.jobGroup = jobGroup; } + + public void updateMember(Member anonymous) { + this.member = anonymous; + } } \ No newline at end of file From 44b4701ad4d60e05069109c00f89bb847f92f0b3 Mon Sep 17 00:00:00 2001 From: dudxo Date: Tue, 22 Oct 2024 23:03:49 +0900 Subject: [PATCH 16/24] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EC=8B=9C=20=EC=97=B0=EA=B4=80=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=A0=9C=EA=B1=B0=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C,=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=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 --- .../answer/repository/AnswerRepository.java | 7 ++- .../repository/CreditHistoryRepository.java | 6 ++ .../member/repository/MemberRepository.java | 17 +++--- .../repository/NotificationRepository.java | 5 +- .../repository/InteractionRepository.java | 5 ++ .../repository/QuestionPostRepository.java | 5 ++ .../security/service/OAuth2UnlinkService.java | 55 ++++++++++++------- 7 files changed, 68 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java index 998bfcf0..a2a8b318 100644 --- a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java +++ b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java @@ -1,13 +1,18 @@ package com.dnd.gongmuin.answer.repository; +import java.util.List; + import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.dnd.gongmuin.answer.domain.Answer; +import com.dnd.gongmuin.member.domain.Member; @Repository public interface AnswerRepository extends JpaRepository { Slice findByQuestionPostId(Long questionPostId); -} + + List findAllByMember(Member member); +} \ No newline at end of file diff --git a/src/main/java/com/dnd/gongmuin/credit_history/repository/CreditHistoryRepository.java b/src/main/java/com/dnd/gongmuin/credit_history/repository/CreditHistoryRepository.java index 8723b117..079e1351 100644 --- a/src/main/java/com/dnd/gongmuin/credit_history/repository/CreditHistoryRepository.java +++ b/src/main/java/com/dnd/gongmuin/credit_history/repository/CreditHistoryRepository.java @@ -1,10 +1,16 @@ package com.dnd.gongmuin.credit_history.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.dnd.gongmuin.credit_history.domain.CreditHistory; +import com.dnd.gongmuin.member.domain.Member; @Repository public interface CreditHistoryRepository extends JpaRepository { + void deleteByMember(Member member); + + List findAllByMember(Member loginMember); } diff --git a/src/main/java/com/dnd/gongmuin/member/repository/MemberRepository.java b/src/main/java/com/dnd/gongmuin/member/repository/MemberRepository.java index e3251fda..1151ee40 100644 --- a/src/main/java/com/dnd/gongmuin/member/repository/MemberRepository.java +++ b/src/main/java/com/dnd/gongmuin/member/repository/MemberRepository.java @@ -1,21 +1,22 @@ package com.dnd.gongmuin.member.repository; +import com.dnd.gongmuin.member.domain.Member; import java.util.Optional; - import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.dnd.gongmuin.member.domain.Member; - @Repository public interface MemberRepository extends JpaRepository, MemberCustom { - Optional findBySocialEmail(String socialEmail); + Optional findBySocialEmail(String socialEmail); + + boolean existsByNickname(String nickname); + + boolean existsByOfficialEmail(String officialEmail); - boolean existsByNickname(String nickname); + boolean existsBySocialEmail(String socialEmail); - boolean existsByOfficialEmail(String officialEmail); + Member findByOfficialEmail(String officialEmail); - boolean existsBySocialEmail(String socialEmail); + Optional findByRole(String role); - Member findByOfficialEmail(String officialEmail); } diff --git a/src/main/java/com/dnd/gongmuin/notification/repository/NotificationRepository.java b/src/main/java/com/dnd/gongmuin/notification/repository/NotificationRepository.java index 7b5b963b..674e00ea 100644 --- a/src/main/java/com/dnd/gongmuin/notification/repository/NotificationRepository.java +++ b/src/main/java/com/dnd/gongmuin/notification/repository/NotificationRepository.java @@ -1,10 +1,11 @@ package com.dnd.gongmuin.notification.repository; +import com.dnd.gongmuin.member.domain.Member; +import com.dnd.gongmuin.notification.domain.Notification; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.dnd.gongmuin.notification.domain.Notification; - @Repository public interface NotificationRepository extends JpaRepository, NotificationCustom { + void deleteByMember(Member member); } diff --git a/src/main/java/com/dnd/gongmuin/post_interaction/repository/InteractionRepository.java b/src/main/java/com/dnd/gongmuin/post_interaction/repository/InteractionRepository.java index aa6e2721..1a5d1bb1 100644 --- a/src/main/java/com/dnd/gongmuin/post_interaction/repository/InteractionRepository.java +++ b/src/main/java/com/dnd/gongmuin/post_interaction/repository/InteractionRepository.java @@ -1,5 +1,6 @@ package com.dnd.gongmuin.post_interaction.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -18,4 +19,8 @@ boolean existsByQuestionPostIdAndMemberIdAndTypeAndIsInteractedTrue( Optional findByQuestionPostIdAndMemberIdAndType( Long questionPostId, Long memberId, InteractionType type ); + + void deleteByMemberId(Long memberId); + + List findAllByMemberId(Long memberId); } diff --git a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java index f416d496..902cbe35 100644 --- a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java +++ b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java @@ -1,11 +1,16 @@ package com.dnd.gongmuin.question_post.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import com.dnd.gongmuin.member.domain.Member; import com.dnd.gongmuin.question_post.domain.QuestionPost; @Repository public interface QuestionPostRepository extends JpaRepository, QuestionPostQueryRepository { boolean existsById(Long id); + + List findAllByMember(Member member); } \ No newline at end of file diff --git a/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java b/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java index 8b03720c..2b1102fc 100644 --- a/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java +++ b/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java @@ -1,11 +1,14 @@ package com.dnd.gongmuin.security.service; +import static com.dnd.gongmuin.member.domain.Provider.*; + import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import com.dnd.gongmuin.common.exception.runtime.ValidationException; @@ -31,9 +34,9 @@ public class OAuth2UnlinkService { private String NAVER_CLIENT_SECRET; public void unlink(String provider) { - if (provider.startsWith("kakao")) { + if (provider.startsWith(KAKAO.getLabel())) { kakaoUnlink(provider); - } else if (provider.startsWith("naver")) { + } else if (provider.startsWith(NAVER.getLabel())) { naverUnlink(provider); } else { throw new ValidationException(OAuth2ErrorCode.INVALID_REQUEST); @@ -46,19 +49,25 @@ public void kakaoUnlink(String provider) { if (accessToken == null) { throw new ValidationException(OAuth2ErrorCode.EXPIRED_AUTH_TOKEN); } + HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); HttpEntity entity = new HttpEntity<>("", headers); - ResponseEntity responseEntity = restTemplate.exchange( - KAKAO_URL, - HttpMethod.POST, - entity, - String.class - ); - - if (responseEntity.getBody().isEmpty()) { + try { + ResponseEntity responseEntity = restTemplate.exchange( + KAKAO_URL, + HttpMethod.POST, + entity, + String.class + ); + + if (responseEntity.getBody().isEmpty()) { + throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); + } + } catch (HttpClientErrorException.Unauthorized e) { throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); } + } public void naverUnlink(String provider) { @@ -69,19 +78,23 @@ public void naverUnlink(String provider) { throw new ValidationException(OAuth2ErrorCode.EXPIRED_AUTH_TOKEN); } - String url = NAVER_URL + - "?service_provider=NAVER" + - "&grant_type=delete" + - "&client_id=" + - NAVER_CLIENT_ID + - "&client_secret=" + - NAVER_CLIENT_SECRET + - "&access_token=" + - accessToken; + try { + String url = NAVER_URL + + "?service_provider=NAVER" + + "&grant_type=delete" + + "&client_id=" + + NAVER_CLIENT_ID + + "&client_secret=" + + NAVER_CLIENT_SECRET + + "&access_token=" + + accessToken; - NaverUnlinkResponse response = restTemplate.getForObject(url, NaverUnlinkResponse.class); + NaverUnlinkResponse response = restTemplate.getForObject(url, NaverUnlinkResponse.class); - if (response != null && !"success".equalsIgnoreCase(response.getResult())) { + if (response != null && !"success".equalsIgnoreCase(response.getResult())) { + throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); + } + } catch (HttpClientErrorException.Unauthorized e) { throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); } } From a492b6212ca481ee73240989df778e6b68b62aac Mon Sep 17 00:00:00 2001 From: dudxo Date: Tue, 22 Oct 2024 23:04:08 +0900 Subject: [PATCH 17/24] =?UTF-8?q?[test]=20:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EB=8B=A8=EC=9C=84=ED=85=8C=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 --- .../auth/service/AuthServiceTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java b/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java index f6092b9e..8af7c1d5 100644 --- a/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java +++ b/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java @@ -5,6 +5,7 @@ import java.time.Duration; import java.util.Date; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Assertions; @@ -19,20 +20,30 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import com.dnd.gongmuin.answer.domain.Answer; +import com.dnd.gongmuin.answer.repository.AnswerRepository; import com.dnd.gongmuin.auth.dto.request.AdditionalInfoRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.ValidateNickNameResponse; +import com.dnd.gongmuin.common.fixture.AnswerFixture; import com.dnd.gongmuin.common.fixture.MemberFixture; +import com.dnd.gongmuin.common.fixture.QuestionPostFixture; +import com.dnd.gongmuin.credit_history.repository.CreditHistoryRepository; import com.dnd.gongmuin.member.domain.JobCategory; import com.dnd.gongmuin.member.domain.JobGroup; import com.dnd.gongmuin.member.domain.Member; import com.dnd.gongmuin.member.repository.MemberRepository; +import com.dnd.gongmuin.notification.repository.NotificationRepository; +import com.dnd.gongmuin.post_interaction.repository.InteractionRepository; +import com.dnd.gongmuin.question_post.domain.QuestionPost; +import com.dnd.gongmuin.question_post.repository.QuestionPostRepository; import com.dnd.gongmuin.redis.util.RedisUtil; import com.dnd.gongmuin.security.jwt.util.CookieUtil; import com.dnd.gongmuin.security.jwt.util.TokenProvider; import com.dnd.gongmuin.security.oauth2.CustomOauth2User; +import com.dnd.gongmuin.security.service.OAuth2UnlinkService; import jakarta.servlet.http.Cookie; @@ -41,6 +52,18 @@ class AuthServiceTest { @Mock private MemberRepository memberRepository; + @Mock + private OAuth2UnlinkService oAuth2UnlinkService; + @Mock + private CreditHistoryRepository creditHistoryRepository; + @Mock + private QuestionPostRepository questionPostRepository; + @Mock + private AnswerRepository answerRepository; + @Mock + private InteractionRepository interactionRepository; + @Mock + private NotificationRepository notificationRepository; @Mock private TokenProvider tokenProvider; @@ -169,4 +192,65 @@ void reissue() { .containsExactly(tuple("Authorization", "reissueToken")) ); } + + @DisplayName("회원 삭제 시 크레딧 내역, 알림, 상호작용은 모두 삭제된다.") + @Test + void deleteMemberWithAssociatedDatas() { + // given + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); + + Member principal = MemberFixture.member(1L); + Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); + + given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); + given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); + given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); + given(redisUtil.getValues(anyString())).willReturn("delete"); + given(memberRepository.findByRole("ROLE_ANONYMOUS")).willReturn(Optional.of(principal)); + + doNothing().when(creditHistoryRepository).deleteByMember(principal); + doNothing().when(notificationRepository).deleteByMember(principal); + doNothing().when(interactionRepository).deleteByMemberId(principal.getId()); + doNothing().when(memberRepository).delete(principal); + + // when + authService.deleteMember(mockRequest); + + // then + verify(creditHistoryRepository).deleteByMember(principal); + verify(notificationRepository).deleteByMember(principal); + verify(interactionRepository).deleteByMemberId(principal.getId()); + verify(memberRepository).delete(principal); + } + + @DisplayName("회원 삭제 시 연관된 답변과 게시글은 모두 익명 회원 정보로 교체된다.") + @Test + void deleteMember() { + // given + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); + + Member principal = MemberFixture.member(1L); + Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); + + List mockQuestionPosts = List.of(QuestionPostFixture.questionPost(1L, principal)); + List mockAnswers = List.of(AnswerFixture.answer(1L, principal)); + + given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); + given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); + given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); + given(redisUtil.getValues(anyString())).willReturn("delete"); + given(memberRepository.findByRole("ROLE_ANONYMOUS")).willReturn(Optional.of(principal)); + + given(questionPostRepository.findAllByMember(principal)).willReturn(mockQuestionPosts); + given(answerRepository.findAllByMember(principal)).willReturn(mockAnswers); + + // when + authService.deleteMember(mockRequest); + + // then + verify(questionPostRepository).findAllByMember(principal); + verify(answerRepository).findAllByMember(principal); + } } \ No newline at end of file From fd17cac603d1fcf359caad0fc7ddea7a17b1e1c9 Mon Sep 17 00:00:00 2001 From: dudxo Date: Tue, 22 Oct 2024 23:04:22 +0900 Subject: [PATCH 18/24] =?UTF-8?q?[test]=20:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=ED=86=B5=ED=95=A9=ED=85=8C=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 --- .../auth/controller/AuthControllerTest.java | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/dnd/gongmuin/auth/controller/AuthControllerTest.java b/src/test/java/com/dnd/gongmuin/auth/controller/AuthControllerTest.java index 9118f151..af976938 100644 --- a/src/test/java/com/dnd/gongmuin/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/dnd/gongmuin/auth/controller/AuthControllerTest.java @@ -1,26 +1,46 @@ package com.dnd.gongmuin.auth.controller; +import static org.assertj.core.api.Assertions.*; import static org.springframework.http.MediaType.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.util.Date; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.test.util.ReflectionTestUtils; +import com.dnd.gongmuin.answer.domain.Answer; +import com.dnd.gongmuin.answer.repository.AnswerRepository; import com.dnd.gongmuin.auth.dto.request.AdditionalInfoRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; +import com.dnd.gongmuin.common.fixture.AnswerFixture; +import com.dnd.gongmuin.common.fixture.CreditHistoryFixture; +import com.dnd.gongmuin.common.fixture.InteractionFixture; import com.dnd.gongmuin.common.fixture.MemberFixture; +import com.dnd.gongmuin.common.fixture.QuestionPostFixture; import com.dnd.gongmuin.common.support.ApiTestSupport; +import com.dnd.gongmuin.credit_history.domain.CreditHistory; +import com.dnd.gongmuin.credit_history.domain.CreditType; +import com.dnd.gongmuin.credit_history.repository.CreditHistoryRepository; import com.dnd.gongmuin.member.domain.Member; import com.dnd.gongmuin.member.repository.MemberRepository; +import com.dnd.gongmuin.notification.repository.NotificationRepository; +import com.dnd.gongmuin.post_interaction.domain.Interaction; +import com.dnd.gongmuin.post_interaction.domain.InteractionType; +import com.dnd.gongmuin.post_interaction.repository.InteractionRepository; +import com.dnd.gongmuin.question_post.domain.QuestionPost; +import com.dnd.gongmuin.question_post.repository.QuestionPostRepository; import com.dnd.gongmuin.security.jwt.util.TokenProvider; import com.dnd.gongmuin.security.oauth2.AuthInfo; import com.dnd.gongmuin.security.oauth2.CustomOauth2User; +import com.dnd.gongmuin.security.service.OAuth2UnlinkService; import jakarta.servlet.http.Cookie; @@ -29,10 +49,20 @@ class AuthControllerTest extends ApiTestSupport { @Autowired private MemberRepository memberRepository; - + @Autowired + private CreditHistoryRepository creditHistoryRepository; + @Autowired + private QuestionPostRepository questionPostRepository; + @Autowired + private AnswerRepository answerRepository; + @Autowired + private InteractionRepository interactionRepository; + @Autowired + private NotificationRepository notificationRepository; + @MockBean + private OAuth2UnlinkService oAuth2UnlinkService; @Autowired private RedisTemplate redisTemplate; - @Autowired private TokenProvider tokenProvider; @@ -113,4 +143,44 @@ void reissue() throws Exception { .andExpect(status().isOk()) .andExpect(cookie().exists("Authorization")); } + + @DisplayName("회원 탈퇴 시 정보를 삭제한다.") + @Test + void deleteMember() throws Exception { + // given + Member anonymous = MemberFixture.member4(); + ReflectionTestUtils.setField(anonymous, "role", "ROLE_ANONYMOUS"); + ReflectionTestUtils.setField(loginMember, "role", "ROLE_ANONYMOUS"); + memberRepository.save(anonymous); + + QuestionPost questionPost1 = QuestionPostFixture.questionPost(1L, loginMember); + QuestionPost questionPost2 = QuestionPostFixture.questionPost(2L, loginMember); + questionPostRepository.saveAll(List.of(questionPost1, questionPost2)); + + Answer answer1 = AnswerFixture.answer(questionPost1.getId(), loginMember); + Answer answer2 = AnswerFixture.answer(questionPost2.getId(), loginMember); + answerRepository.saveAll(List.of(answer1, answer2)); + + CreditHistory creditHistory = CreditHistoryFixture.creditHistory(CreditType.CHOOSE, 1000, loginMember); + creditHistoryRepository.save(creditHistory); + + Interaction interaction1 = InteractionFixture.interaction(InteractionType.RECOMMEND, loginMember.getId(), + questionPost1.getId()); + Interaction interaction2 = InteractionFixture.interaction(InteractionType.SAVED, loginMember.getId(), + questionPost2.getId()); + interactionRepository.saveAll(List.of(interaction1, interaction2)); + + // when // then + mockMvc.perform(post("/api/auth/delete") + .contentType(APPLICATION_JSON) + .cookie(accessToken) + ) + .andExpect(status().isOk()); + + assertThat(memberRepository.findById(loginMember.getId())).isEmpty(); + assertThat(questionPostRepository.findAllByMember(loginMember)).isEmpty(); + assertThat(answerRepository.findAllByMember(loginMember)).isEmpty(); + assertThat(creditHistoryRepository.findAllByMember(loginMember)).isEmpty(); + assertThat(interactionRepository.findAllByMemberId(loginMember.getId())).isEmpty(); + } } From c14d3e32e7ef134e3c636c941275b4d42bb24c36 Mon Sep 17 00:00:00 2001 From: dudxo Date: Tue, 22 Oct 2024 23:10:16 +0900 Subject: [PATCH 19/24] =?UTF-8?q?[test]=20:=20MemberFixture=20Member=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20socialEmail?= =?UTF-8?q?=20=EA=B0=92=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A1=9C=EC=A7=81=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 --- .../com/dnd/gongmuin/member/service/MemberServiceTest.java | 4 ++-- .../java/com/dnd/gongmuin/security/jwt/TokenProviderTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/dnd/gongmuin/member/service/MemberServiceTest.java b/src/test/java/com/dnd/gongmuin/member/service/MemberServiceTest.java index 324c9829..1921bda4 100644 --- a/src/test/java/com/dnd/gongmuin/member/service/MemberServiceTest.java +++ b/src/test/java/com/dnd/gongmuin/member/service/MemberServiceTest.java @@ -71,8 +71,8 @@ void getMemberBySocialEmail() { // then assertThat(findMember).extracting("socialName", "socialEmail") .containsExactlyInAnyOrder( - "회원123", - "KAKAO123/gongmuin@daum.net" + member.getSocialName(), + member.getSocialEmail() ); } diff --git a/src/test/java/com/dnd/gongmuin/security/jwt/TokenProviderTest.java b/src/test/java/com/dnd/gongmuin/security/jwt/TokenProviderTest.java index c7029ce0..7dcd84d0 100644 --- a/src/test/java/com/dnd/gongmuin/security/jwt/TokenProviderTest.java +++ b/src/test/java/com/dnd/gongmuin/security/jwt/TokenProviderTest.java @@ -111,7 +111,7 @@ void getAuthentication() { // then assertThat(authentication.isAuthenticated()).isTrue(); - assertThat(principal.getSocialEmail()).isEqualTo("KAKAO123/gongmuin@daum.net"); + assertThat(principal.getSocialEmail()).isEqualTo(member.getSocialEmail()); } @DisplayName("토큰의 만료일이 현재 시간보다 전이면 만료된 토큰이다.") From bf9f77a1f378c9969b50953e554562a73a50511c Mon Sep 17 00:00:00 2001 From: dudxo Date: Thu, 24 Oct 2024 17:46:53 +0900 Subject: [PATCH 20/24] =?UTF-8?q?[feat]=20:=20Bulk=20update=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/gongmuin/answer/repository/AnswerRepository.java | 6 ++++++ .../question_post/repository/QuestionPostRepository.java | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java index a2a8b318..95e74806 100644 --- a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java +++ b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java @@ -4,6 +4,8 @@ import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import com.dnd.gongmuin.answer.domain.Answer; @@ -15,4 +17,8 @@ public interface AnswerRepository extends JpaRepository { Slice findByQuestionPostId(Long questionPostId); List findAllByMember(Member member); + + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("UPDATE Answer a SET a.member = :anonymous WHERE a.member.id = :memberId") + public void updateAnswers(Long memberId, Member anonymous); } \ No newline at end of file diff --git a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java index 902cbe35..54f28dc2 100644 --- a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java +++ b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java @@ -3,6 +3,8 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import com.dnd.gongmuin.member.domain.Member; @@ -13,4 +15,8 @@ public interface QuestionPostRepository extends JpaRepository findAllByMember(Member member); + + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("UPDATE QuestionPost q SET q.member = :anonymous WHERE q.member.id = :memberId") + public void updateQuestionPosts(Long memberId, Member anonymous); } \ No newline at end of file From e71e62f9dba677d89ad4c4adb8eba088ec5c87cb Mon Sep 17 00:00:00 2001 From: dudxo Date: Thu, 24 Oct 2024 17:47:24 +0900 Subject: [PATCH 21/24] =?UTF-8?q?[refactor]=20:=20=EB=8B=A4=EC=A4=91=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84=ED=95=9C=20Bulk=20updat?= =?UTF-8?q?e=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=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 --- .../com/dnd/gongmuin/auth/service/AuthService.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java index 2792fa80..ea8bba6e 100644 --- a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java +++ b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java @@ -2,14 +2,12 @@ import java.time.Duration; import java.util.Date; -import java.util.List; import java.util.Objects; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.dnd.gongmuin.answer.domain.Answer; import com.dnd.gongmuin.answer.repository.AnswerRepository; import com.dnd.gongmuin.auth.dto.request.AdditionalInfoRequest; import com.dnd.gongmuin.auth.dto.request.TempSignInRequest; @@ -32,7 +30,6 @@ import com.dnd.gongmuin.member.repository.MemberRepository; import com.dnd.gongmuin.notification.repository.NotificationRepository; import com.dnd.gongmuin.post_interaction.repository.InteractionRepository; -import com.dnd.gongmuin.question_post.domain.QuestionPost; import com.dnd.gongmuin.question_post.repository.QuestionPostRepository; import com.dnd.gongmuin.redis.util.RedisUtil; import com.dnd.gongmuin.security.jwt.util.CookieUtil; @@ -249,12 +246,8 @@ private void replaceWithAnonymous(Member member) { Member anonymous = memberRepository.findByRole(ANONYMOUS) .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); - List posts = questionPostRepository.findAllByMember(member); - posts.forEach(post -> post.updateMember(anonymous)); - - List answers = answerRepository.findAllByMember(member); - answers.forEach(answer -> answer.updateMember(anonymous)); - + questionPostRepository.updateQuestionPosts(member.getId(), anonymous); + answerRepository.updateAnswers(member.getId(), anonymous); } } From b9f03f3f5c35a3b8867bb585033934d379413d5b Mon Sep 17 00:00:00 2001 From: dudxo Date: Thu, 24 Oct 2024 17:53:56 +0900 Subject: [PATCH 22/24] =?UTF-8?q?[test]=20:=20=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9D=B5=EB=AA=85=20=ED=9A=8C=EC=9B=90=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/auth/service/AuthServiceTest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java b/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java index 8af7c1d5..99c97ffd 100644 --- a/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java +++ b/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java @@ -243,14 +243,11 @@ void deleteMember() { given(redisUtil.getValues(anyString())).willReturn("delete"); given(memberRepository.findByRole("ROLE_ANONYMOUS")).willReturn(Optional.of(principal)); - given(questionPostRepository.findAllByMember(principal)).willReturn(mockQuestionPosts); - given(answerRepository.findAllByMember(principal)).willReturn(mockAnswers); - // when authService.deleteMember(mockRequest); // then - verify(questionPostRepository).findAllByMember(principal); - verify(answerRepository).findAllByMember(principal); + verify(questionPostRepository).updateQuestionPosts(any(), any()); + verify(answerRepository).updateAnswers(any(), any()); } } \ No newline at end of file From b7cde2e7b621f5ec046b3b9e5c0f1b4b9838de1f Mon Sep 17 00:00:00 2001 From: dudxo Date: Mon, 4 Nov 2024 21:38:35 +0900 Subject: [PATCH 23/24] =?UTF-8?q?[style]=20:=20update=20query=20method=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../answer/repository/AnswerRepository.java | 16 +- .../gongmuin/auth/service/AuthService.java | 337 +++++++------ .../repository/QuestionPostRepository.java | 16 +- .../auth/service/AuthServiceTest.java | 443 +++++++++--------- 4 files changed, 404 insertions(+), 408 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java index 95e74806..f7527e99 100644 --- a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java +++ b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java @@ -1,24 +1,22 @@ package com.dnd.gongmuin.answer.repository; +import com.dnd.gongmuin.answer.domain.Answer; +import com.dnd.gongmuin.member.domain.Member; import java.util.List; - import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import com.dnd.gongmuin.answer.domain.Answer; -import com.dnd.gongmuin.member.domain.Member; - @Repository public interface AnswerRepository extends JpaRepository { - Slice findByQuestionPostId(Long questionPostId); + Slice findByQuestionPostId(Long questionPostId); - List findAllByMember(Member member); + List findAllByMember(Member member); - @Modifying(flushAutomatically = true, clearAutomatically = true) - @Query("UPDATE Answer a SET a.member = :anonymous WHERE a.member.id = :memberId") - public void updateAnswers(Long memberId, Member anonymous); + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("UPDATE Answer a SET a.member = :anonymous WHERE a.member.id = :memberId") + public void updateAnswersMember(Long memberId, Member member); } \ No newline at end of file diff --git a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java index ea8bba6e..7c6e630b 100644 --- a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java +++ b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java @@ -1,13 +1,5 @@ package com.dnd.gongmuin.auth.service; -import java.time.Duration; -import java.util.Date; -import java.util.Objects; - -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.dnd.gongmuin.answer.repository.AnswerRepository; import com.dnd.gongmuin.auth.dto.request.AdditionalInfoRequest; import com.dnd.gongmuin.auth.dto.request.TempSignInRequest; @@ -37,217 +29,222 @@ import com.dnd.gongmuin.security.oauth2.AuthInfo; import com.dnd.gongmuin.security.oauth2.CustomOauth2User; import com.dnd.gongmuin.security.service.OAuth2UnlinkService; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.time.Duration; +import java.util.Date; +import java.util.Objects; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class AuthService { - private static final String LOGOUT = "logout"; - private static final String DELETE = "delete"; - private static final String ANONYMOUS = "ROLE_ANONYMOUS"; - private final TokenProvider tokenProvider; - private final MemberRepository memberRepository; - private final CookieUtil cookieUtil; - private final RedisUtil redisUtil; - private final OAuth2UnlinkService oAuth2UnlinkService; - private final CreditHistoryRepository creditHistoryRepository; - private final QuestionPostRepository questionPostRepository; - private final AnswerRepository answerRepository; - private final InteractionRepository interactionRepository; - private final NotificationRepository notificationRepository; + private static final String LOGOUT = "logout"; + private static final String DELETE = "delete"; + private static final String ANONYMOUS = "ROLE_ANONYMOUS"; + private final TokenProvider tokenProvider; + private final MemberRepository memberRepository; + private final CookieUtil cookieUtil; + private final RedisUtil redisUtil; + private final OAuth2UnlinkService oAuth2UnlinkService; + private final CreditHistoryRepository creditHistoryRepository; + private final QuestionPostRepository questionPostRepository; + private final AnswerRepository answerRepository; + private final InteractionRepository interactionRepository; + private final NotificationRepository notificationRepository; - @Transactional - public TempSignResponse tempSignUp(TempSignUpRequest tempSignUpRequest, HttpServletResponse response) { - Date now = new Date(); - Member member = Member.of( - tempSignUpRequest.socialName(), - "kakao/" + tempSignUpRequest.socialEmail(), - 10000, - "ROLE_GUEST" - ); + @Transactional + public TempSignResponse tempSignUp(TempSignUpRequest tempSignUpRequest, HttpServletResponse response) { + Date now = new Date(); + Member member = Member.of( + tempSignUpRequest.socialName(), + "kakao/" + tempSignUpRequest.socialEmail(), + 10000, + "ROLE_GUEST" + ); - if (memberRepository.existsBySocialEmail(member.getSocialEmail())) { - throw new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER); - } + if (memberRepository.existsBySocialEmail(member.getSocialEmail())) { + throw new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER); + } - memberRepository.save(member); + memberRepository.save(member); - AuthInfo authInfo = AuthInfo.of(member.getSocialName(), member.getSocialEmail(), member.getRole()); - CustomOauth2User customOauth2User = new CustomOauth2User(authInfo); + AuthInfo authInfo = AuthInfo.of(member.getSocialName(), member.getSocialEmail(), member.getRole()); + CustomOauth2User customOauth2User = new CustomOauth2User(authInfo); - tokenProvider.generateRefreshToken(customOauth2User, now); - String accessToken = tokenProvider.generateAccessToken(customOauth2User, now); - response.addCookie(cookieUtil.createCookie(accessToken)); + tokenProvider.generateRefreshToken(customOauth2User, now); + String accessToken = tokenProvider.generateAccessToken(customOauth2User, now); + response.addCookie(cookieUtil.createCookie(accessToken)); - return new TempSignResponse(true); - } + return new TempSignResponse(true); + } - @Transactional - public TempSignResponse tempSignIn(TempSignInRequest tempSignInRequest, HttpServletResponse response) { - Date now = new Date(); + @Transactional + public TempSignResponse tempSignIn(TempSignInRequest tempSignInRequest, HttpServletResponse response) { + Date now = new Date(); - String prefixSocialEmail = "kakao/" + tempSignInRequest.socialEmail(); + String prefixSocialEmail = "kakao/" + tempSignInRequest.socialEmail(); - Member member = memberRepository.findBySocialEmail(prefixSocialEmail) - .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); + Member member = memberRepository.findBySocialEmail(prefixSocialEmail) + .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); - AuthInfo authInfo = AuthInfo.of(member.getSocialName(), member.getSocialEmail(), member.getRole()); - CustomOauth2User customOauth2User = new CustomOauth2User(authInfo); + AuthInfo authInfo = AuthInfo.of(member.getSocialName(), member.getSocialEmail(), member.getRole()); + CustomOauth2User customOauth2User = new CustomOauth2User(authInfo); - tokenProvider.generateRefreshToken(customOauth2User, now); - String accessToken = tokenProvider.generateAccessToken(customOauth2User, now); - response.addCookie(cookieUtil.createCookie(accessToken)); + tokenProvider.generateRefreshToken(customOauth2User, now); + String accessToken = tokenProvider.generateAccessToken(customOauth2User, now); + response.addCookie(cookieUtil.createCookie(accessToken)); - return new TempSignResponse(true); - } + return new TempSignResponse(true); + } - @Transactional(readOnly = true) - public ValidateNickNameResponse isDuplicatedNickname(ValidateNickNameRequest request) { - boolean isDuplicated = memberRepository.existsByNickname(request.nickname()); + @Transactional(readOnly = true) + public ValidateNickNameResponse isDuplicatedNickname(ValidateNickNameRequest request) { + boolean isDuplicated = memberRepository.existsByNickname(request.nickname()); - return new ValidateNickNameResponse(isDuplicated); - } + return new ValidateNickNameResponse(isDuplicated); + } - @Transactional - public SignUpResponse signUp(AdditionalInfoRequest request, String email, HttpServletResponse response) { - Member foundMember = memberRepository.findBySocialEmail(email) - .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); + @Transactional + public SignUpResponse signUp(AdditionalInfoRequest request, String email, HttpServletResponse response) { + Member foundMember = memberRepository.findBySocialEmail(email) + .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); - if (!isOfficialEmail(foundMember)) { - throw new NotFoundException(MemberErrorCode.NOT_FOUND_NEW_MEMBER); - } + if (!isOfficialEmail(foundMember)) { + throw new NotFoundException(MemberErrorCode.NOT_FOUND_NEW_MEMBER); + } - updateAdditionalInfo(request, foundMember); + updateAdditionalInfo(request, foundMember); - cookieUtil.deleteCookie(response); + cookieUtil.deleteCookie(response); - return new SignUpResponse(foundMember.getNickname()); - } + return new SignUpResponse(foundMember.getNickname()); + } - public LogoutResponse logout(HttpServletRequest request, HttpServletResponse response) { - String accessToken = cookieUtil.getCookieValue(request); + public LogoutResponse logout(HttpServletRequest request, HttpServletResponse response) { + String accessToken = cookieUtil.getCookieValue(request); - if (!tokenProvider.validateToken(accessToken, new Date())) { - throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); - } + if (!tokenProvider.validateToken(accessToken, new Date())) { + throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); + } - Authentication authentication = tokenProvider.getAuthentication(accessToken); - Member member = (Member)authentication.getPrincipal(); + Authentication authentication = tokenProvider.getAuthentication(accessToken); + Member member = (Member) authentication.getPrincipal(); - if (!Objects.isNull(redisUtil.getValues("RT:" + member.getSocialEmail()))) { - redisUtil.deleteValues("RT:" + member.getSocialEmail()); - } + if (!Objects.isNull(redisUtil.getValues("RT:" + member.getSocialEmail()))) { + redisUtil.deleteValues("RT:" + member.getSocialEmail()); + } - Long expiration = tokenProvider.getExpiration(accessToken, new Date()); - redisUtil.setValues(accessToken, LOGOUT, Duration.ofMillis(expiration)); - - String values = redisUtil.getValues(accessToken); - if (!Objects.equals(values, LOGOUT)) { - throw new NotFoundException(MemberErrorCode.LOGOUT_FAILED); - } - - cookieUtil.deleteCookie(response); + Long expiration = tokenProvider.getExpiration(accessToken, new Date()); + redisUtil.setValues(accessToken, LOGOUT, Duration.ofMillis(expiration)); + + String values = redisUtil.getValues(accessToken); + if (!Objects.equals(values, LOGOUT)) { + throw new NotFoundException(MemberErrorCode.LOGOUT_FAILED); + } + + cookieUtil.deleteCookie(response); - return new LogoutResponse(true); - } + return new LogoutResponse(true); + } - public ReissueResponse reissue(HttpServletRequest request, HttpServletResponse response) { - String accessToken = cookieUtil.getCookieValue(request); + public ReissueResponse reissue(HttpServletRequest request, HttpServletResponse response) { + String accessToken = cookieUtil.getCookieValue(request); - // 로그아웃 토큰 처리 - if ("logout".equals(redisUtil.getValues(accessToken))) { - throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); - } + // 로그아웃 토큰 처리 + if ("logout".equals(redisUtil.getValues(accessToken))) { + throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); + } - Authentication authentication = tokenProvider.getAuthentication(accessToken); - Member member = (Member)authentication.getPrincipal(); + Authentication authentication = tokenProvider.getAuthentication(accessToken); + Member member = (Member) authentication.getPrincipal(); - String refreshToken = redisUtil.getValues("RT:" + member.getSocialEmail()); + String refreshToken = redisUtil.getValues("RT:" + member.getSocialEmail()); - // 로그아웃 또는 토큰 만료 경우 처리 - if ("false".equals(refreshToken)) { - throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); - } + // 로그아웃 또는 토큰 만료 경우 처리 + if ("false".equals(refreshToken)) { + throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); + } - CustomOauth2User customUser = new CustomOauth2User( - AuthInfo.of(member.getSocialName(), member.getSocialEmail(), member.getRole())); - String reissuedAccessToken = tokenProvider.generateAccessToken(customUser, new Date()); - tokenProvider.generateRefreshToken(customUser, new Date()); + CustomOauth2User customUser = new CustomOauth2User( + AuthInfo.of(member.getSocialName(), member.getSocialEmail(), member.getRole())); + String reissuedAccessToken = tokenProvider.generateAccessToken(customUser, new Date()); + tokenProvider.generateRefreshToken(customUser, new Date()); - response.addCookie(cookieUtil.createCookie(reissuedAccessToken)); + response.addCookie(cookieUtil.createCookie(reissuedAccessToken)); - return new ReissueResponse(true); - } + return new ReissueResponse(true); + } - public boolean isOfficialEmail(Member member) { - return Objects.isNull(member.getOfficialEmail()); - } + public boolean isOfficialEmail(Member member) { + return Objects.isNull(member.getOfficialEmail()); + } - private void updateAdditionalInfo(AdditionalInfoRequest request, Member findMember) { - findMember.updateAdditionalInfo( - request.nickname(), - request.officialEmail(), - JobGroup.from(request.jobGroup()), - JobCategory.from(request.jobCategory()) - ); - } + private void updateAdditionalInfo(AdditionalInfoRequest request, Member findMember) { + findMember.updateAdditionalInfo( + request.nickname(), + request.officialEmail(), + JobGroup.from(request.jobGroup()), + JobCategory.from(request.jobCategory()) + ); + } - @Transactional - public DeleteMemberResponse deleteMember(HttpServletRequest request) { - String accessToken = cookieUtil.getCookieValue(request); + @Transactional + public DeleteMemberResponse deleteMember(HttpServletRequest request) { + String accessToken = cookieUtil.getCookieValue(request); - if (!tokenProvider.validateToken(accessToken, new Date())) { - throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); - } + if (!tokenProvider.validateToken(accessToken, new Date())) { + throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); + } - Authentication authentication = tokenProvider.getAuthentication(accessToken); - Member member = (Member)authentication.getPrincipal(); + Authentication authentication = tokenProvider.getAuthentication(accessToken); + Member member = (Member) authentication.getPrincipal(); - // RefreshToken 삭제 - if (!Objects.isNull(redisUtil.getValues("RT:" + member.getSocialEmail()))) { - redisUtil.deleteValues("RT:" + member.getSocialEmail()); - } + // RefreshToken 삭제 + if (!Objects.isNull(redisUtil.getValues("RT:" + member.getSocialEmail()))) { + redisUtil.deleteValues("RT:" + member.getSocialEmail()); + } - // 현재 발급 되어 있는 AccessToken 블랙리스트 등록 - Long expiration = tokenProvider.getExpiration(accessToken, new Date()); - redisUtil.setValues(accessToken, DELETE, Duration.ofMillis(expiration)); + // 현재 발급 되어 있는 AccessToken 블랙리스트 등록 + Long expiration = tokenProvider.getExpiration(accessToken, new Date()); + redisUtil.setValues(accessToken, DELETE, Duration.ofMillis(expiration)); - // AccessToken 블랙리스트 등록 여부 검증 - String values = redisUtil.getValues(accessToken); - if (!Objects.equals(values, DELETE)) { - throw new NotFoundException(MemberErrorCode.DELETE_FAILED); - } + // AccessToken 블랙리스트 등록 여부 검증 + String values = redisUtil.getValues(accessToken); + if (!Objects.equals(values, DELETE)) { + throw new NotFoundException(MemberErrorCode.DELETE_FAILED); + } - deleteAssociation(member); - replaceWithAnonymous(member); - memberRepository.delete(member); - - // oauth2 서비스 연결 끊기 - oAuth2UnlinkService.unlink(member.getSocialEmail()); - - // oauth2 access 토큰 삭제 - if (redisUtil.getValues("AT(oauth):" + member.getSocialEmail()) != null) { - redisUtil.deleteValues("AT(oauth):" + member.getSocialEmail()); - } - return new DeleteMemberResponse(member.getId()); - } - - private void deleteAssociation(Member member) { - creditHistoryRepository.deleteByMember(member); - notificationRepository.deleteByMember(member); - interactionRepository.deleteByMemberId(member.getId()); - } - - private void replaceWithAnonymous(Member member) { - Member anonymous = memberRepository.findByRole(ANONYMOUS) - .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); - - questionPostRepository.updateQuestionPosts(member.getId(), anonymous); - answerRepository.updateAnswers(member.getId(), anonymous); - } + deleteAssociation(member); + replaceWithAnonymous(member); + memberRepository.delete(member); + + // oauth2 서비스 연결 끊기 + oAuth2UnlinkService.unlink(member.getSocialEmail()); + + // oauth2 access 토큰 삭제 + if (redisUtil.getValues("AT(oauth):" + member.getSocialEmail()) != null) { + redisUtil.deleteValues("AT(oauth):" + member.getSocialEmail()); + } + return new DeleteMemberResponse(member.getId()); + } + + private void deleteAssociation(Member member) { + creditHistoryRepository.deleteByMember(member); + notificationRepository.deleteByMember(member); + interactionRepository.deleteByMemberId(member.getId()); + } + + private void replaceWithAnonymous(Member member) { + Member anonymous = memberRepository.findByRole(ANONYMOUS) + .orElseThrow(() -> new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER)); + + questionPostRepository.updateQuestionPostsMember(member.getId(), anonymous); + answerRepository.updateAnswersMember(member.getId(), anonymous); + } } diff --git a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java index 54f28dc2..837e0f0f 100644 --- a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java +++ b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java @@ -1,22 +1,20 @@ package com.dnd.gongmuin.question_post.repository; +import com.dnd.gongmuin.member.domain.Member; +import com.dnd.gongmuin.question_post.domain.QuestionPost; import java.util.List; - import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import com.dnd.gongmuin.member.domain.Member; -import com.dnd.gongmuin.question_post.domain.QuestionPost; - @Repository public interface QuestionPostRepository extends JpaRepository, QuestionPostQueryRepository { - boolean existsById(Long id); + boolean existsById(Long id); - List findAllByMember(Member member); + List findAllByMember(Member member); - @Modifying(flushAutomatically = true, clearAutomatically = true) - @Query("UPDATE QuestionPost q SET q.member = :anonymous WHERE q.member.id = :memberId") - public void updateQuestionPosts(Long memberId, Member anonymous); + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("UPDATE QuestionPost q SET q.member = :anonymous WHERE q.member.id = :memberId") + public void updateQuestionPostsMember(Long memberId, Member member); } \ No newline at end of file diff --git a/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java b/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java index 99c97ffd..0c1a48e0 100644 --- a/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java +++ b/src/test/java/com/dnd/gongmuin/auth/service/AuthServiceTest.java @@ -1,24 +1,13 @@ package com.dnd.gongmuin.auth.service; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.BDDMockito.*; - -import java.time.Duration; -import java.util.Date; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.anyString; +import static org.mockito.BDDMockito.doNothing; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.willDoNothing; import com.dnd.gongmuin.answer.domain.Answer; import com.dnd.gongmuin.answer.repository.AnswerRepository; @@ -44,210 +33,224 @@ import com.dnd.gongmuin.security.jwt.util.TokenProvider; import com.dnd.gongmuin.security.oauth2.CustomOauth2User; import com.dnd.gongmuin.security.service.OAuth2UnlinkService; - import jakarta.servlet.http.Cookie; +import java.time.Duration; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; @ExtendWith(MockitoExtension.class) class AuthServiceTest { - @Mock - private MemberRepository memberRepository; - @Mock - private OAuth2UnlinkService oAuth2UnlinkService; - @Mock - private CreditHistoryRepository creditHistoryRepository; - @Mock - private QuestionPostRepository questionPostRepository; - @Mock - private AnswerRepository answerRepository; - @Mock - private InteractionRepository interactionRepository; - @Mock - private NotificationRepository notificationRepository; - - @Mock - private TokenProvider tokenProvider; - - @Mock - private RedisUtil redisUtil; - - @Mock - private CookieUtil cookieUtil; - - @InjectMocks - private AuthService authService; - - @DisplayName("공무원 이메일이 존재하는지 체크한다.") - @Test - void isOfficialEmail() { - // given - Member kakaoMember = MemberFixture.member(); - - // when - boolean result = authService.isOfficialEmail(kakaoMember); - - // then - assertThat(result).isFalse(); - - } - - @DisplayName("중복 닉네임이 존재하는지 체크한다.") - @Test - void isDuplicatedNickname() { - // given - given(memberRepository.existsByNickname("김철수")).willReturn(true); - ValidateNickNameRequest request = new ValidateNickNameRequest("김철수"); - - // when - ValidateNickNameResponse duplicatedNickname = authService.isDuplicatedNickname(request); - - // then - assertThat(duplicatedNickname.isDuplicated()).isTrue(); - } - - @DisplayName("신규 회원은 추가 정보가 업데이트 된다.") - @Test - void signUp() { - // given - AdditionalInfoRequest request = new AdditionalInfoRequest("abc123@korea.com", "김신규", "공업", "일반기계"); - MockHttpServletResponse mockResponse = new MockHttpServletResponse(); - - Member member1 = MemberFixture.member3(); - given(memberRepository.findBySocialEmail(member1.getSocialEmail())).willReturn( - Optional.of(member1)); - - // when - authService.signUp(request, member1.getSocialEmail(), mockResponse); - - // then - assertThat(member1).extracting("officialEmail", "nickname", "jobGroup", "jobCategory") - .containsExactlyInAnyOrder( - "abc123@korea.com", - "김신규", - JobGroup.ENG, - JobCategory.GME - ); - } - - @DisplayName("로그인 회원은 로그아웃 할 수 있다.") - @Test - void logout() { - // given - Long fiveMinutes = 300_000L; - - Member principal = MemberFixture.member(); - Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); - - MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - MockHttpServletResponse mockResponse = new MockHttpServletResponse(); - mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); - - given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); - given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); - given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); - given(tokenProvider.getExpiration(anyString(), any(Date.class))).willReturn(fiveMinutes); - given(redisUtil.getValues(anyString())).willReturn("refresh"); - given(redisUtil.getValues(anyString())).willReturn("logout"); - - willDoNothing().given(redisUtil).setValues(anyString(), anyString(), any(Duration.class)); - - // when - LogoutResponse response = authService.logout(mockRequest, mockResponse); - - // then - assertThat(response.result()).isTrue(); - assertThat(mockResponse.getCookies()).isEmpty(); - } - - @DisplayName("refresh 토큰이 만료되지 않았다면 재발급 할 수 있다.") - @Test - void reissue() { - // given - MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - MockHttpServletResponse mockResponse = new MockHttpServletResponse(); - mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); - - Member principal = MemberFixture.member(); - Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); - - given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); - given(cookieUtil.createCookie(anyString())).willReturn(new Cookie("Authorization", "reissueToken")); - given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); - given(redisUtil.getValues(anyString())).willReturn("refreshToken"); - given(tokenProvider.generateAccessToken(any(CustomOauth2User.class), any(Date.class))).willReturn( - "reissueToken"); - given(tokenProvider.generateRefreshToken(any(CustomOauth2User.class), any(Date.class))).willReturn( - "reissueToken"); - - // when - ReissueResponse response = authService.reissue(mockRequest, mockResponse); - Cookie[] cookies = mockResponse.getCookies(); - - // then - Assertions.assertAll( - () -> assertThat(response.result()).isTrue(), - () -> assertThat(cookies) - .hasSize(1) - .extracting(Cookie::getName, Cookie::getValue) - .containsExactly(tuple("Authorization", "reissueToken")) - ); - } - - @DisplayName("회원 삭제 시 크레딧 내역, 알림, 상호작용은 모두 삭제된다.") - @Test - void deleteMemberWithAssociatedDatas() { - // given - MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); - - Member principal = MemberFixture.member(1L); - Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); - - given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); - given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); - given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); - given(redisUtil.getValues(anyString())).willReturn("delete"); - given(memberRepository.findByRole("ROLE_ANONYMOUS")).willReturn(Optional.of(principal)); - - doNothing().when(creditHistoryRepository).deleteByMember(principal); - doNothing().when(notificationRepository).deleteByMember(principal); - doNothing().when(interactionRepository).deleteByMemberId(principal.getId()); - doNothing().when(memberRepository).delete(principal); - - // when - authService.deleteMember(mockRequest); - - // then - verify(creditHistoryRepository).deleteByMember(principal); - verify(notificationRepository).deleteByMember(principal); - verify(interactionRepository).deleteByMemberId(principal.getId()); - verify(memberRepository).delete(principal); - } - - @DisplayName("회원 삭제 시 연관된 답변과 게시글은 모두 익명 회원 정보로 교체된다.") - @Test - void deleteMember() { - // given - MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); - - Member principal = MemberFixture.member(1L); - Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); - - List mockQuestionPosts = List.of(QuestionPostFixture.questionPost(1L, principal)); - List mockAnswers = List.of(AnswerFixture.answer(1L, principal)); - - given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); - given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); - given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); - given(redisUtil.getValues(anyString())).willReturn("delete"); - given(memberRepository.findByRole("ROLE_ANONYMOUS")).willReturn(Optional.of(principal)); - - // when - authService.deleteMember(mockRequest); - - // then - verify(questionPostRepository).updateQuestionPosts(any(), any()); - verify(answerRepository).updateAnswers(any(), any()); - } + @Mock + private MemberRepository memberRepository; + @Mock + private OAuth2UnlinkService oAuth2UnlinkService; + @Mock + private CreditHistoryRepository creditHistoryRepository; + @Mock + private QuestionPostRepository questionPostRepository; + @Mock + private AnswerRepository answerRepository; + @Mock + private InteractionRepository interactionRepository; + @Mock + private NotificationRepository notificationRepository; + + @Mock + private TokenProvider tokenProvider; + + @Mock + private RedisUtil redisUtil; + + @Mock + private CookieUtil cookieUtil; + + @InjectMocks + private AuthService authService; + + @DisplayName("공무원 이메일이 존재하는지 체크한다.") + @Test + void isOfficialEmail() { + // given + Member kakaoMember = MemberFixture.member(); + + // when + boolean result = authService.isOfficialEmail(kakaoMember); + + // then + assertThat(result).isFalse(); + + } + + @DisplayName("중복 닉네임이 존재하는지 체크한다.") + @Test + void isDuplicatedNickname() { + // given + given(memberRepository.existsByNickname("김철수")).willReturn(true); + ValidateNickNameRequest request = new ValidateNickNameRequest("김철수"); + + // when + ValidateNickNameResponse duplicatedNickname = authService.isDuplicatedNickname(request); + + // then + assertThat(duplicatedNickname.isDuplicated()).isTrue(); + } + + @DisplayName("신규 회원은 추가 정보가 업데이트 된다.") + @Test + void signUp() { + // given + AdditionalInfoRequest request = new AdditionalInfoRequest("abc123@korea.com", "김신규", "공업", "일반기계"); + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + + Member member1 = MemberFixture.member3(); + given(memberRepository.findBySocialEmail(member1.getSocialEmail())).willReturn( + Optional.of(member1)); + + // when + authService.signUp(request, member1.getSocialEmail(), mockResponse); + + // then + assertThat(member1).extracting("officialEmail", "nickname", "jobGroup", "jobCategory") + .containsExactlyInAnyOrder( + "abc123@korea.com", + "김신규", + JobGroup.ENG, + JobCategory.GME + ); + } + + @DisplayName("로그인 회원은 로그아웃 할 수 있다.") + @Test + void logout() { + // given + Long fiveMinutes = 300_000L; + + Member principal = MemberFixture.member(); + Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); + + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); + + given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); + given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); + given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); + given(tokenProvider.getExpiration(anyString(), any(Date.class))).willReturn(fiveMinutes); + given(redisUtil.getValues(anyString())).willReturn("refresh"); + given(redisUtil.getValues(anyString())).willReturn("logout"); + + willDoNothing().given(redisUtil).setValues(anyString(), anyString(), any(Duration.class)); + + // when + LogoutResponse response = authService.logout(mockRequest, mockResponse); + + // then + assertThat(response.result()).isTrue(); + assertThat(mockResponse.getCookies()).isEmpty(); + } + + @DisplayName("refresh 토큰이 만료되지 않았다면 재발급 할 수 있다.") + @Test + void reissue() { + // given + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); + + Member principal = MemberFixture.member(); + Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); + + given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); + given(cookieUtil.createCookie(anyString())).willReturn(new Cookie("Authorization", "reissueToken")); + given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); + given(redisUtil.getValues(anyString())).willReturn("refreshToken"); + given(tokenProvider.generateAccessToken(any(CustomOauth2User.class), any(Date.class))).willReturn( + "reissueToken"); + given(tokenProvider.generateRefreshToken(any(CustomOauth2User.class), any(Date.class))).willReturn( + "reissueToken"); + + // when + ReissueResponse response = authService.reissue(mockRequest, mockResponse); + Cookie[] cookies = mockResponse.getCookies(); + + // then + Assertions.assertAll( + () -> assertThat(response.result()).isTrue(), + () -> assertThat(cookies) + .hasSize(1) + .extracting(Cookie::getName, Cookie::getValue) + .containsExactly(tuple("Authorization", "reissueToken")) + ); + } + + @DisplayName("회원 삭제 시 크레딧 내역, 알림, 상호작용은 모두 삭제된다.") + @Test + void deleteMemberWithAssociatedDatas() { + // given + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); + + Member principal = MemberFixture.member(1L); + Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); + + given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); + given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); + given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); + given(redisUtil.getValues(anyString())).willReturn("delete"); + given(memberRepository.findByRole("ROLE_ANONYMOUS")).willReturn(Optional.of(principal)); + + doNothing().when(creditHistoryRepository).deleteByMember(principal); + doNothing().when(notificationRepository).deleteByMember(principal); + doNothing().when(interactionRepository).deleteByMemberId(principal.getId()); + doNothing().when(memberRepository).delete(principal); + + // when + authService.deleteMember(mockRequest); + + // then + verify(creditHistoryRepository).deleteByMember(principal); + verify(notificationRepository).deleteByMember(principal); + verify(interactionRepository).deleteByMemberId(principal.getId()); + verify(memberRepository).delete(principal); + } + + @DisplayName("회원 삭제 시 연관된 답변과 게시글은 모두 익명 회원 정보로 교체된다.") + @Test + void deleteMember() { + // given + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setCookies(new Cookie("Authorization", "testtesttesttest")); + + Member principal = MemberFixture.member(1L); + Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "test"); + + List mockQuestionPosts = List.of(QuestionPostFixture.questionPost(1L, principal)); + List mockAnswers = List.of(AnswerFixture.answer(1L, principal)); + + given(cookieUtil.getCookieValue(mockRequest)).willReturn("testtesttesttest"); + given(tokenProvider.validateToken(anyString(), any(Date.class))).willReturn(true); + given(tokenProvider.getAuthentication(anyString())).willReturn(authentication); + given(redisUtil.getValues(anyString())).willReturn("delete"); + given(memberRepository.findByRole("ROLE_ANONYMOUS")).willReturn(Optional.of(principal)); + + // when + authService.deleteMember(mockRequest); + + // then + verify(questionPostRepository).updateQuestionPostsMember(any(), any()); + verify(answerRepository).updateAnswersMember(any(), any()); + } } \ No newline at end of file From 1ea0bfa8f2be9405ef1101c8fc2d7262a14fd5ad Mon Sep 17 00:00:00 2001 From: dudxo Date: Mon, 4 Nov 2024 21:44:40 +0900 Subject: [PATCH 24/24] =?UTF-8?q?[fix]=20:=20=EB=A7=A4=EA=B0=9C=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20query=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/answer/repository/AnswerRepository.java | 2 +- .../question_post/repository/QuestionPostRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java index f7527e99..552f456b 100644 --- a/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java +++ b/src/main/java/com/dnd/gongmuin/answer/repository/AnswerRepository.java @@ -17,6 +17,6 @@ public interface AnswerRepository extends JpaRepository { List findAllByMember(Member member); @Modifying(flushAutomatically = true, clearAutomatically = true) - @Query("UPDATE Answer a SET a.member = :anonymous WHERE a.member.id = :memberId") + @Query("UPDATE Answer a SET a.member = :member WHERE a.member.id = :memberId") public void updateAnswersMember(Long memberId, Member member); } \ No newline at end of file diff --git a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java index 837e0f0f..bcad75fb 100644 --- a/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java +++ b/src/main/java/com/dnd/gongmuin/question_post/repository/QuestionPostRepository.java @@ -15,6 +15,6 @@ public interface QuestionPostRepository extends JpaRepository findAllByMember(Member member); @Modifying(flushAutomatically = true, clearAutomatically = true) - @Query("UPDATE QuestionPost q SET q.member = :anonymous WHERE q.member.id = :memberId") + @Query("UPDATE QuestionPost q SET q.member = :member WHERE q.member.id = :memberId") public void updateQuestionPostsMember(Long memberId, Member member); } \ No newline at end of file