Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/#220 회원 탈퇴 로직 리팩터링 #227

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class AdminController {

private static final String REQUEST_WITHOUT_SESSION_MESSAGE = "SESSION 값이 존재하지 않습니다.";
private static final String ADMIN_SESSION_ATTRIBUTE = "adminId";

private final AdminService adminService;
private final ReportService reportService;
private final InquiryService inquiryService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,4 @@ public ResponseEntity<Void> logout(@Login AuthInfo authInfo) {
return ResponseEntity.ok().build();
}

@PostMapping("/delete")
public ResponseEntity<Void> delete(@Login AuthInfo authInfo) {
authService.delete(authInfo.userId());
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@
import coffeemeet.server.auth.domain.JwtTokenProvider;
import coffeemeet.server.auth.implement.RefreshTokenCommand;
import coffeemeet.server.common.execption.InvalidAuthException;
import coffeemeet.server.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AuthService {

private static final String EXPIRED_REFRESH_TOKEN_MESSAGE = "리프레시 토큰(%s)이 만료되었습니다. 다시 로그인해 주세요.";

private final UserService userService;
private final AuthTokensGenerator authTokensGenerator;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenCommand refreshTokenCommand;
Expand All @@ -37,10 +34,4 @@ public void logout(Long userId) {
refreshTokenCommand.deleteRefreshToken(userId);
}

@Transactional
public void delete(Long userId) {
userService.deleteUser(userId);
refreshTokenCommand.deleteRefreshToken(userId);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package coffeemeet.server.common.config;

import coffeemeet.server.user.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@EnableScheduling
@RequiredArgsConstructor
public class ScheduleConfig {

private final UserService userService;

@Scheduled(cron = "0 0 3 * * *")
public void checkUserDeleted() {
log.info("탈퇴 후 30일이 지난 회원 정보 제거");
userService.deleteUserInfos();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class UserArgumentResolver implements HandlerMethodArgumentResolver {

private static final int AUTHENTICATION_PREFIX_LENGTH = 7;
private static final String HEADER_AUTHENTICATION_FAILED_MESSAGE = "(%s)는 잘못된 권한 헤더입니다.";

private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenQuery refreshTokenQuery;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
import org.springframework.stereotype.Component;

@Component
public class OAuthMemberClientComposite {
public class OAuthMemberClientRegistry {

private static final String INVALID_LOGIN_TYPE_MESSAGE = "로그인 타입(%s)에 일치하는 타입이 없습니다.";

private final Map<OAuthProvider, OAuthMemberClient> mapping;

public OAuthMemberClientComposite(Set<OAuthMemberClient> clients) {
public OAuthMemberClientRegistry(Set<OAuthMemberClient> clients) {
this.mapping = clients.stream().collect(
Collectors.toUnmodifiableMap(OAuthMemberClient::oAuthProvider, Function.identity())
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package coffeemeet.server.oauth.implement.client;

import static coffeemeet.server.auth.exception.AuthErrorCode.INVALID_LOGIN_TYPE;

import coffeemeet.server.common.execption.InvalidAuthException;
import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail;
import coffeemeet.server.user.domain.OAuthProvider;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;

@Component
public class OAuthMemberUnlinkRegistry {

private static final String INVALID_LOGIN_TYPE_MESSAGE = "로그인 타입(%s)에 일치하는 타입이 없습니다.";

private final Map<OAuthProvider, OAuthUnlinkClient> mapping;

public OAuthMemberUnlinkRegistry(Set<OAuthUnlinkClient> details) {
this.mapping = details.stream().collect(
Collectors.toUnmodifiableMap(OAuthUnlinkClient::oAuthProvider, Function.identity())
);
}

public OAuthUnlinkDetail unlink(OAuthProvider oAuthProvider, String accessToken) {
return getClient(oAuthProvider).unlink(accessToken);
}

private OAuthUnlinkClient getClient(OAuthProvider oAuthProvider) {
return Optional.ofNullable(mapping.get(oAuthProvider))
.orElseThrow(() -> new InvalidAuthException(
INVALID_LOGIN_TYPE,
String.format(INVALID_LOGIN_TYPE_MESSAGE, oAuthProvider))
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package coffeemeet.server.oauth.implement.client;

import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail;
import coffeemeet.server.user.domain.OAuthProvider;

public interface OAuthUnlinkClient {

OAuthProvider oAuthProvider();

OAuthUnlinkDetail unlink(String accessToken);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import coffeemeet.server.oauth.domain.OAuthMemberDetail;
import coffeemeet.server.oauth.implement.client.OAuthMemberClient;
import coffeemeet.server.oauth.infrastructure.kakao.KakaoClient;
import coffeemeet.server.oauth.infrastructure.kakao.KakaoFetchClient;
import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoMemberDetail;
import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoTokens;
import coffeemeet.server.user.domain.OAuthProvider;
Expand All @@ -13,7 +13,7 @@
@RequiredArgsConstructor
public class KakaoMemberClient implements OAuthMemberClient {

private final KakaoClient kakaoClient;
private final KakaoFetchClient kakaoClient;

@Override
public OAuthProvider oAuthProvider() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import coffeemeet.server.oauth.domain.OAuthMemberDetail;
import coffeemeet.server.oauth.implement.client.OAuthMemberClient;
import coffeemeet.server.oauth.infrastructure.naver.NaverClient;
import coffeemeet.server.oauth.infrastructure.naver.NaverFetchClient;
import coffeemeet.server.oauth.infrastructure.naver.dto.NaverMemberDetail;
import coffeemeet.server.oauth.infrastructure.naver.dto.NaverTokens;
import coffeemeet.server.user.domain.OAuthProvider;
Expand All @@ -13,7 +13,7 @@
@RequiredArgsConstructor
public class NaverMemberClient implements OAuthMemberClient {

private final NaverClient naverClient;
private final NaverFetchClient naverClient;

@Override
public OAuthProvider oAuthProvider() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
import org.springframework.stereotype.Component;

@Component
public class AuthCodeRequestUrlProviderComposite {
public class AuthCodeRequestUrlProviderRegistry {

private static final String INVALID_LOGIN_TYPE_MESSAGE = "로그인 타입(%s)에 일치하는 타입이 없습니다.";
private final Map<OAuthProvider, AuthCodeRequestUrlProvider> mapping;

public AuthCodeRequestUrlProviderComposite(Set<AuthCodeRequestUrlProvider> providers) {
public AuthCodeRequestUrlProviderRegistry(Set<AuthCodeRequestUrlProvider> providers) {
this.mapping = providers.stream().collect(
Collectors.toUnmodifiableMap(
AuthCodeRequestUrlProvider::oAuthProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package coffeemeet.server.oauth.infrastructure;

public record OAuthUnlinkDetail(
Long id,
String accessToken,
String result
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

@Component
@RequiredArgsConstructor
public class KakaoClient {
public class KakaoFetchClient {

private static final String REQUEST_TOKEN_URL = "https://kauth.kakao.com/oauth/token";
private static final String REQUEST_INFO_URL = "https://kapi.kakao.com/v2/user/me";
Expand Down Expand Up @@ -58,7 +58,6 @@ public KakaoMemberDetail fetchMember(String accessToken) {
httpHeaders.set(AUTHORIZATION, BEARER_TYPE + accessToken);

HttpEntity<?> request = new HttpEntity<>(httpHeaders);

KakaoMemberDetail response = restTemplate.exchange(REQUEST_INFO_URL, HttpMethod.GET, request,
KakaoMemberDetail.class).getBody();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package coffeemeet.server.oauth.infrastructure.kakao;

import static coffeemeet.server.oauth.utils.constant.OAuthConstant.AUTHORIZATION;
import static coffeemeet.server.oauth.utils.constant.OAuthConstant.BEARER_TYPE;

import coffeemeet.server.oauth.implement.client.OAuthUnlinkClient;
import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail;
import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoUnlinkDetail;
import coffeemeet.server.user.domain.OAuthProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;

@Component
@RequiredArgsConstructor
public class KakaoUnlinkClient implements OAuthUnlinkClient {

private static final String UNLINK_USER_URL = "https://kapi.kakao.com/v1/user/unlink";

private final RestTemplate restTemplate;

@Override
public OAuthProvider oAuthProvider() {
return OAuthProvider.KAKAO;
}

public OAuthUnlinkDetail unlink(String accessToken) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
httpHeaders.set(AUTHORIZATION, BEARER_TYPE + accessToken);

HttpEntity<?> request = new HttpEntity<>(httpHeaders);

KakaoUnlinkDetail response = restTemplate.exchange(UNLINK_USER_URL, HttpMethod.POST, request,
KakaoUnlinkDetail.class).getBody();

assert response != null;
return response.toOAuthUnlinkDetail();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package coffeemeet.server.oauth.infrastructure.kakao.dto;

import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail;

public record KakaoUnlinkDetail(Long userId) {

public OAuthUnlinkDetail toOAuthUnlinkDetail() {
return new OAuthUnlinkDetail(this.userId(), null, null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

@Component
@RequiredArgsConstructor
public class NaverClient {
public class NaverFetchClient {

private static final String REQUEST_TOKEN_URL = "https://nid.naver.com/oauth2.0/token";
private static final String REQUEST_INFO_URL = "https://openapi.naver.com/v1/nid/me";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package coffeemeet.server.oauth.infrastructure.naver;

import static coffeemeet.server.oauth.utils.constant.OAuthConstant.AUTHORIZATION;
import static coffeemeet.server.oauth.utils.constant.OAuthConstant.AUTHORIZATION_CODE;
import static coffeemeet.server.oauth.utils.constant.OAuthConstant.BEARER_TYPE;
import static coffeemeet.server.oauth.utils.constant.OAuthConstant.CLIENT_ID;
import static coffeemeet.server.oauth.utils.constant.OAuthConstant.CLIENT_SECRET;
import static coffeemeet.server.oauth.utils.constant.OAuthConstant.GRANT_TYPE;

import coffeemeet.server.oauth.config.naver.NaverProperties;
import coffeemeet.server.oauth.implement.client.OAuthUnlinkClient;
import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail;
import coffeemeet.server.oauth.infrastructure.naver.dto.NaverUnlinkDetail;
import coffeemeet.server.user.domain.OAuthProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
@RequiredArgsConstructor
public class NaverUnlinkClient implements OAuthUnlinkClient {

private static final String UNLINK_USER_URL = "https://nid.naver.com/oauth2.0/token";

private final RestTemplate restTemplate;
private final NaverProperties naverProperties;

@Override
public OAuthProvider oAuthProvider() {
return OAuthProvider.NAVER;
}

public OAuthUnlinkDetail unlink(String accessToken) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

httpHeaders.set(AUTHORIZATION, BEARER_TYPE + accessToken);
httpHeaders.set(CLIENT_ID, naverProperties.getClientId());
httpHeaders.set(CLIENT_SECRET, naverProperties.getClientSecret());
httpHeaders.set(GRANT_TYPE, AUTHORIZATION_CODE);

HttpEntity<?> request = new HttpEntity<>(httpHeaders);

NaverUnlinkDetail response = restTemplate.exchange(UNLINK_USER_URL, HttpMethod.POST, request,
NaverUnlinkDetail.class).getBody();

assert response != null;
return response.toOAuthUnlinkDetail();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package coffeemeet.server.oauth.infrastructure.naver.dto;

import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail;

public record NaverUnlinkDetail(
String accessToken,
String result
) {

public OAuthUnlinkDetail toOAuthUnlinkDetail() {
return new OAuthUnlinkDetail(null, this.accessToken, this.result);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package coffeemeet.server.oauth.presentation;

import coffeemeet.server.common.annotation.Login;
import coffeemeet.server.common.domain.AuthInfo;
import coffeemeet.server.oauth.service.OAuthService;
import coffeemeet.server.user.domain.OAuthProvider;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -9,6 +11,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -27,4 +30,10 @@ public ResponseEntity<Void> redirectAuthCodeRequestUrl(@PathVariable OAuthProvid
return new ResponseEntity<>(HttpStatus.FOUND);
}

@PostMapping("/delete")
public ResponseEntity<Void> unlink(@Login AuthInfo authInfo, OAuthProvider oAuthProvider) {
oAuthService.unlink(authInfo.userId(), authInfo.refreshToken(), oAuthProvider);
return ResponseEntity.ok().build();
}

}
Loading