Skip to content

Commit

Permalink
Merge pull request #730 from woowacourse-teams/fix/#721
Browse files Browse the repository at this point in the history
사용자 전환 시 구글, 애플 회원을 논리 삭제로 구현
  • Loading branch information
ksk0605 authored Oct 23, 2024
2 parents 2ad4eda + 9cd08da commit 3712d4b
Show file tree
Hide file tree
Showing 19 changed files with 137 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import mouda.backend.auth.implement.AppleUserInfoProvider;
import mouda.backend.auth.implement.JoinManager;
import mouda.backend.auth.implement.jwt.AccessTokenProvider;
import mouda.backend.auth.presentation.response.LoginResponse;
import mouda.backend.member.domain.Member;
import mouda.backend.member.domain.OauthType;
import mouda.backend.member.implement.MemberFinder;
Expand All @@ -22,29 +23,29 @@ public class AppleAuthService {
private final MemberFinder memberFinder;
private final AccessTokenProvider accessTokenProvider;

public String login(String idToken, String user) {
public LoginResponse login(String idToken, String user) {
String identifier = userInfoProvider.getIdentifier(idToken);
if (user != null) {
return handleNewUser(user, identifier);
}
return handleExistingUser(identifier);
}

private String handleNewUser(String user, String identifier) {
private LoginResponse handleNewUser(String user, String identifier) {
Member joinedMember = join(identifier, user);
return accessTokenProvider.provide(joinedMember);
return new LoginResponse(accessTokenProvider.provide(joinedMember), joinedMember.isConverted());
}

private Member join(String identifier, String user) {
String name = userInfoProvider.getName(user);
return joinManager.join(name, OauthType.APPLE, identifier);
}

private String handleExistingUser(String identifier) {
Member member = memberFinder.getByIdentifier(identifier);
private LoginResponse handleExistingUser(String identifier) {
Member member = memberFinder.findActiveOrDeletedByIdentifier(identifier);
if (member != null) {
joinManager.rejoin(member);
return accessTokenProvider.provide(member);
return new LoginResponse(accessTokenProvider.provide(member), member.isConverted());
}
throw new AuthException(HttpStatus.BAD_REQUEST, AuthErrorMessage.CANNOT_FIND_APPLE_MEMBER);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mouda.backend.auth.business;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import mouda.backend.auth.implement.GoogleUserInfoProvider;
Expand All @@ -14,6 +15,7 @@
import mouda.backend.member.implement.MemberWriter;

@Service
@Transactional
@RequiredArgsConstructor
public class GoogleAuthService {

Expand All @@ -26,14 +28,14 @@ public class GoogleAuthService {
public LoginResponse login(GoogleLoginRequest request) {
String name = userInfoProvider.getName(request.idToken());
String identifier = userInfoProvider.getIdentifier(request.idToken());
Member member = memberFinder.getByIdentifier(identifier);
Member member = memberFinder.findActiveOrDeletedByIdentifier(identifier);

if (member != null) {
joinManager.rejoin(member);
memberWriter.updateName(member.getId(), name);
return new LoginResponse(accessTokenProvider.provide(member));
return new LoginResponse(accessTokenProvider.provide(member), member.isConverted());
}
Member joinedMember = joinManager.join(name, OauthType.GOOGLE, identifier);
return new LoginResponse(accessTokenProvider.provide(joinedMember));
return new LoginResponse(accessTokenProvider.provide(joinedMember), joinedMember.isConverted());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mouda.backend.auth.business;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import mouda.backend.auth.implement.KakaoUserInfoProvider;
Expand All @@ -10,6 +11,7 @@
import mouda.backend.member.implement.MemberWriter;

@Service
@Transactional
@RequiredArgsConstructor
public class KakaoAuthService {

Expand All @@ -19,8 +21,9 @@ public class KakaoAuthService {

public void convert(Member alternation, KakaoConvertRequest kakaoConvertRequest) {
String identifier = userInfoProvider.getIdentifier(kakaoConvertRequest.code());
Member target = memberFinder.findByIdentifier(identifier);
memberWriter.updateLoginDetail(target.getId(), alternation.getLoginDetail());
memberWriter.delete(alternation);
Member kakao = memberFinder.findActiveOrDeletedByIdentifier(identifier);
memberWriter.updateLoginDetail(kakao.getId(), alternation.getLoginDetail());
kakao.convert();
memberWriter.deprecate(alternation);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class TestAuthService {
private final AccessTokenProvider accessTokenProvider;

public LoginResponse basicLoginAnna() {
Member member = memberFinder.findByIdentifier("identifier");
return new LoginResponse(accessTokenProvider.provide(member));
Member member = memberFinder.findActiveOrDeletedByIdentifier("identifier");
return new LoginResponse(accessTokenProvider.provide(member), true);
}

public LoginResponse basicLoginHogee() {
Expand All @@ -32,6 +32,6 @@ public LoginResponse basicLoginHogee() {
.loginDetail(new LoginDetail(OauthType.GOOGLE, UUID.randomUUID().toString()))
.build();
memberWriter.append(member);
return new LoginResponse(accessTokenProvider.provide(member));
return new LoginResponse(accessTokenProvider.provide(member), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ public ResponseEntity<Void> loginApple(
@RequestParam("id_token") String idToken,
@RequestParam(name = "user", required = false) String user
) {
String accessToken = appleAuthService.login(idToken, user);
LoginResponse response = appleAuthService.login(idToken, user);

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Location", redirectUrl + accessToken);
httpHeaders.add("Location", String.format(redirectUrl, response.accessToken(), response.isConverted()));
return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mouda.backend.auth.presentation.response;

public record LoginResponse(
String accessToken
String accessToken,
boolean isConverted
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class MemberService {

public Member findMember(String token) {
String socialId = accessTokenProvider.extractSocialId(token);
return memberFinder.findByIdentifier(socialId);
return memberFinder.findActiveOrDeletedByIdentifier(socialId);
}

public void checkAuthentication(String token) {
Expand Down
11 changes: 11 additions & 0 deletions backend/src/main/java/mouda/backend/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ public class Member {
@Enumerated(EnumType.STRING)
private MemberStatus memberStatus;

private boolean isConverted;

@Builder
public Member(String name, LoginDetail loginDetail) {
this.loginDetail = loginDetail;
validateName(name);
this.name = name;
this.memberStatus = MemberStatus.ACTIVE;
this.isConverted = false;
}

private void validateName(String name) {
Expand Down Expand Up @@ -70,6 +73,14 @@ public void rejoin() {
this.memberStatus = MemberStatus.ACTIVE;
}

public void convert() {
this.isConverted = true;
}

public void deprecate() {
this.memberStatus = MemberStatus.DEPRECATED;
}

@Override
public boolean equals(Object o) {
if (this == o)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum MemberStatus {

ACTIVE,
DELETED
DELETED,
DEPRECATED
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ public class MemberFinder {

private final MemberRepository memberRepository;

public Member getByIdentifier(String identifier) {
return memberRepository.findByLoginDetail_Identifier(identifier).orElse(null);
}

public Member findByIdentifier(String identifier) {
return memberRepository.findByLoginDetail_Identifier(identifier)
public Member findActiveOrDeletedByIdentifier(String identifier) {
return memberRepository.findActiveOrDeletedByIdentifier(identifier)
.orElseThrow(() -> new AuthException(HttpStatus.NOT_FOUND, AuthErrorMessage.MEMBER_NOT_FOUND));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public Member append(Member member) {
}

public void updateLoginDetail(long memberId, LoginDetail loginDetail) {
memberRepository.updateLoginDetail(memberId, loginDetail.getOauthType(), loginDetail.getIdentifier());
memberRepository.updateLoginDetail(memberId, loginDetail.getOauthType(),
loginDetail.getIdentifier());
}

public void updateName(long memberId, String name) {
Expand All @@ -33,4 +34,9 @@ public void withdraw(Member member) {
public void delete(Member alternation) {
memberRepository.delete(alternation);
}

public void deprecate(Member member) {
member.deprecate();
memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByLoginDetail_Identifier(String identifier);
@Query("""
SELECT m FROM Member m
WHERE m.loginDetail.identifier = :identifier AND (m.memberStatus = 'ACTIVE' OR m.memberStatus = 'DELETED')
""")
Optional<Member> findActiveOrDeletedByIdentifier(@Param("identifier") String identifier);

@Query("""
SELECT m FROM Member m
WHERE m.loginDetail.identifier = :identifier AND m.memberStatus = 'DEPRECATED'
""")
Optional<Member> findDeprecatedByIdentifier(@Param("identifier") String identifier);

@Query("""
UPDATE Member m
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ oauth:
redirect-uri: http://localhost:8081/oauth/google
apple:
redirect-uri: https://api.dev.mouda.site/v1/auth/apple
redirection: https://dev.mouda.site/oauth/apple?token=
redirection: https://dev.mouda.site/oauth/apple?token=%s&isConverted=%s

aws:
region:
Expand Down
4 changes: 2 additions & 2 deletions backend/src/main/resources/data.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
INSERT INTO darakbang (name, code)
VALUES ('테스트용 다락방', 'MOUDA');

INSERT INTO member (name, oauth_type, identifier)
VALUES ('김민겸', 'KAKAO', 'identifier');
INSERT INTO member (name, oauth_type, identifier, is_converted)
VALUES ('김민겸', 'KAKAO', 'identifier', true);

INSERT INTO darakbang_member (darakbang_id, member_id, nickname, role)
VALUES (1, 1, '안나', 'MANAGER');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.boot.test.mock.mockito.MockBean;

import mouda.backend.auth.implement.AppleUserInfoProvider;
import mouda.backend.auth.presentation.response.LoginResponse;
import mouda.backend.common.fixture.MemberFixture;
import mouda.backend.member.domain.Member;
import mouda.backend.member.domain.MemberStatus;
Expand Down Expand Up @@ -43,11 +44,11 @@ void setUp() {
@Test
void joinAndLogin() {
// when
String accessToken = appleAuthService.login("idToken", "user");
LoginResponse response = appleAuthService.login("idToken", "user");

// then
assertThat(accessToken).isNotNull();
Optional<Member> member = memberRepository.findByLoginDetail_Identifier(identifier);
assertThat(response.accessToken()).isNotNull();
Optional<Member> member = memberRepository.findActiveOrDeletedByIdentifier(identifier);
assertThat(member.isPresent()).isTrue();
assertThat(member.get().getName()).isEqualTo(name);
}
Expand All @@ -60,11 +61,11 @@ void login() {
memberRepository.save(anna);

// when
String accessToken = appleAuthService.login("idToken", null);
LoginResponse response = appleAuthService.login("idToken", null);

// then
assertThat(accessToken).isNotNull();
Optional<Member> member = memberRepository.findByLoginDetail_Identifier(identifier);
assertThat(response.accessToken()).isNotNull();
Optional<Member> member = memberRepository.findActiveOrDeletedByIdentifier(identifier);
assertThat(member.isPresent()).isTrue();
}

Expand All @@ -77,11 +78,11 @@ void rejoinAndLogin() {
memberRepository.save(anna);

// when
String accessToken = appleAuthService.login("idToken", null);
LoginResponse response = appleAuthService.login("idToken", null);

// then
assertThat(accessToken).isNotNull();
Optional<Member> member = memberRepository.findByLoginDetail_Identifier(identifier);
assertThat(response.accessToken()).isNotNull();
Optional<Member> member = memberRepository.findActiveOrDeletedByIdentifier(identifier);
assertThat(member.isPresent()).isTrue();
assertThat(member.get().getMemberStatus()).isEqualTo(MemberStatus.ACTIVE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void processSocialLoginWhoDeletedBefore() {

// then
assertThat(loginResponse.accessToken()).isNotNull();
Optional<Member> member = memberRepository.findByLoginDetail_Identifier("1234");
Optional<Member> member = memberRepository.findActiveOrDeletedByIdentifier("1234");
assertThat(member.isPresent()).isTrue();
assertThat(member.get().getMemberStatus()).isEqualTo(MemberStatus.ACTIVE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import mouda.backend.auth.presentation.request.KakaoConvertRequest;
import mouda.backend.common.fixture.MemberFixture;
import mouda.backend.member.domain.Member;
import mouda.backend.member.domain.MemberStatus;
import mouda.backend.member.domain.OauthType;
import mouda.backend.member.infrastructure.MemberRepository;

Expand All @@ -35,23 +36,25 @@ class KakaoAuthServiceTest {
void convert() {
// given
String kakaoIdentifier = "kakaoIdentifier";
Member anna = MemberFixture.getAnna(kakaoIdentifier);
memberRepository.save(anna);
Member kakao = MemberFixture.getAnna(kakaoIdentifier);
memberRepository.save(kakao);

String appleIdentifier = "appleIdentifier";
Member alternation = MemberFixture.getAnna(OauthType.APPLE, appleIdentifier);
memberRepository.save(alternation);
String googleIdentifier = "googleIdentifier";
Member google = MemberFixture.getAnna(OauthType.APPLE, googleIdentifier);
memberRepository.save(google);
when(userInfoProvider.getIdentifier(anyString())).thenReturn(kakaoIdentifier);

// when
kakaoAuthService.convert(alternation, new KakaoConvertRequest("code"));
kakaoAuthService.convert(google, new KakaoConvertRequest("code"));

// then
Optional<Member> kakaoMember = memberRepository.findByLoginDetail_Identifier(kakaoIdentifier);
assertThat(kakaoMember.isEmpty()).isTrue();
Optional<Member> appleMember = memberRepository.findByLoginDetail_Identifier(appleIdentifier);
assertThat(appleMember.isPresent()).isTrue();
assertThat(appleMember.get().getName()).isEqualTo(anna.getName());
assertThat(appleMember.get().getLoginDetail()).isEqualTo(alternation.getLoginDetail());
Optional<Member> kakaoMember = memberRepository.findActiveOrDeletedByIdentifier(kakaoIdentifier);
assertThat(kakaoMember.isPresent()).isTrue();
assertThat(kakaoMember.get().getMemberStatus()).isEqualTo(MemberStatus.ACTIVE);
Optional<Member> googleMember = memberRepository.findDeprecatedByIdentifier(googleIdentifier);
assertThat(googleMember.isPresent()).isTrue();
assertThat(googleMember.get().getMemberStatus()).isEqualTo(MemberStatus.DEPRECATED);
assertThat(googleMember.get().getLoginDetail()).isEqualTo(google.getLoginDetail());
}
}

Loading

0 comments on commit 3712d4b

Please sign in to comment.