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

Feat(#37): 회원 이름 이메일 저장 #52

Merged
merged 7 commits into from
Feb 4, 2025
Merged
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 @@ -2,30 +2,11 @@

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class KakaoTokenResponse {

@JsonProperty("token_type")
private String tokenType;

@JsonProperty("access_token")
private String accessToken;

@JsonProperty("expires_in")
private Integer expiresIn;

@JsonProperty("refresh_token")
private String refreshToken;

@JsonProperty("refresh_token_expires_in")
private Integer refreshTokenExpiresIn;
}
public record KakaoTokenResponse(
@JsonProperty("token_type") String tokenType,
@JsonProperty("access_token") String accessToken,
@JsonProperty("expires_in") Integer expiresIn,
@JsonProperty("refresh_token") String refreshToken,
@JsonProperty("refresh_token_expires_in") Integer refreshTokenExpiresIn) {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class KakaoUserInfoResponse {
public record KakaoUserInfoResponse(
@JsonProperty("id") Long id, @JsonProperty("kakao_account") KakaoAccount kakaoAccount) {

@JsonProperty("id")
Long id;
@JsonIgnoreProperties(ignoreUnknown = true)
public record KakaoAccount(
@JsonProperty("name") String name, @JsonProperty("email") String email) {}
}
Comment on lines +7 to 13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inner class👍 👍 👍

Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,21 @@ public class OauthService {
public Long kakaoLogin(final KakaoLoginCommand command) {
final KakaoTokenResponse token =
getKakaoToken(command.authorizationCode(), command.redirectUri());
log.info("kakao 토큰 받아오기 완료");
final KakaoUserInfoResponse userInfo = getKakaoUserInfo(token.getAccessToken());
log.info("kakao 사용자 정보 받아오기 완료");
final KakaoUserInfoResponse userInfo = getKakaoUserInfo(token.accessToken());

final String oauthId = userInfo.getId().toString();
final Member member = findKakaoMember(oauthId);
if (member == null) {
return signupKakaoMember(command.authorizationCode()).getId();
}
return member.getId();
}

private Member signupKakaoMember(final String oauthId) {
final Member member = memberRepository.save(Member.create());
socialAccountRepository.save(SocialAccount.kakaoSignup(oauthId, member));
return member;
}

private Member findKakaoMember(final String oauthId) {
final String oauthId = userInfo.id().toString();
return socialAccountRepository
.findByOauthIdAndSocialProviderAndDeletedAtIsNull(oauthId, SocialProvider.KAKAO)
.map(SocialAccount::getMember)
.filter(member -> member.getDeletedAt() == null)
.orElse(null);
.map(
it -> {
it.updateUserInfo(userInfo.kakaoAccount().name(), userInfo.kakaoAccount().email());
return it.getId();
})
.orElseGet(
() ->
kakaoSignUp(
oauthId, userInfo.kakaoAccount().name(), userInfo.kakaoAccount().email()));
}

private KakaoUserInfoResponse getKakaoUserInfo(final String accessToken) {
Expand All @@ -88,6 +79,12 @@ private KakaoTokenResponse getKakaoToken(
return kakaoAuthClient.requestToken(params);
}

private Long kakaoSignUp(final String oauthId, final String name, final String email) {
final Member member = memberRepository.save(Member.create(name, email));
socialAccountRepository.save(SocialAccount.kakaoSignup(oauthId, member));
return member.getId();
}

@Transactional
public Long appleLogin(final AppleLoginCommand command) {
AppleIdToken appleIdToken = AppleIdToken.of(command.idToken());
Expand All @@ -99,12 +96,7 @@ public Long appleLogin(final AppleLoginCommand command) {
authorization.getSub(), SocialProvider.APPLE)
.map(account -> account.getMember().getId())
.orElseGet(
() -> {
final Member member = memberRepository.save(Member.create());
socialAccountRepository.save(
SocialAccount.appleSignUp(authorization.getSub(), member));
return member.getId();
});
() -> appleSignup(authorization.getSub(), command.name(), authorization.getEmail()));
}

private AppleAuthorization decodeAppleIdTokenPayload(final String appleJwtClaims) {
Expand All @@ -116,4 +108,10 @@ private AppleAuthorization decodeAppleIdTokenPayload(final String appleJwtClaims
throw CustomException.INVALID_APPLE_ID_TOKEN;
}
}

private Long appleSignup(final String oauthId, final String name, final String email) {
final Member member = memberRepository.save(Member.create(name, email));
socialAccountRepository.save(SocialAccount.appleSignUp(oauthId, member));
return member.getId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ public class Member extends BaseTimeEntity {
@OneToMany(mappedBy = "member")
private List<SocialAccount> socialAccounts;

public static Member create() {
return new Member();
private String name;

private String email;

private Member(final String name, final String email) {
this.name = name;
this.email = email;
}
Comment on lines +24 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pn2. 별건 아닌데 email 검증해주는건 어떤가요 ?? 아니면 email을 VO로 만들어서 써도 좋을 것 같아여 !

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

시간되면 해주세요! 나중에 리팩토링해도 괜찮구요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 다른 pr에 병목이 생길 것 같아 추후에 이슈 열어서 작업해보겠습니다!


public static Member create(final String name, final String email) {
return new Member(name, email);
}

public void softDelete() {
Expand All @@ -29,4 +38,9 @@ public void softDelete() {
}
super.softDelete();
}

public void updateUserInfo(final String name, final String email) {
this.name = name;
this.email = email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
alter table member
add name varchar(60)
add email varchar(255);
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

import static org.assertj.core.api.Assertions.tuple;
import static org.assertj.core.api.BDDAssertions.then;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.nexters.jaknaesocore.common.support.IntegrationTest;
import org.nexters.jaknaesocore.domain.auth.restclient.dto.KakaoTokenResponse;
import org.nexters.jaknaesocore.domain.auth.restclient.dto.KakaoUserInfoResponse;
import org.nexters.jaknaesocore.domain.auth.restclient.dto.KakaoUserInfoResponse.KakaoAccount;
import org.nexters.jaknaesocore.domain.auth.service.dto.AppleLoginCommand;
import org.nexters.jaknaesocore.domain.auth.service.dto.KakaoLoginCommand;
import org.nexters.jaknaesocore.domain.member.model.Member;
import org.nexters.jaknaesocore.domain.member.repository.MemberRepository;
import org.nexters.jaknaesocore.domain.socialaccount.model.SocialAccount;
Expand All @@ -30,6 +38,23 @@ void tearDown() {
memberRepository.deleteAllInBatch();
}

private SocialAccount createSocialAccount(
final Member member, final String oauthId, final SocialProvider socialProvider) {
return SocialAccountFixture.builder()
.member(member)
.oauthId(oauthId)
.socialProvider(socialProvider)
.build();
}

private AppleLoginCommand createAppleLoginCommand(String idToken, String name) {
return new AppleLoginCommand(idToken, name);
}

private KakaoLoginCommand createKakaoLoginCommand(String authorizationCode, String redirectUri) {
return new KakaoLoginCommand(authorizationCode, redirectUri);
}

Comment on lines +41 to +57
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntelliJ 설정에서 Actions on Save를 사용하고 있는데 자동으로 포맷팅해서 메서드가 클래스 위로 올라와 버렸습니다 😭
가독성에 방해된다면 해당 옵션 끄고 원상 복귀하여 다시 커밋하겠습니다..!

@Nested
@DisplayName("appleLogin 메소드는 ")
class appleLogin {
Expand All @@ -45,7 +70,10 @@ void shouldSignIn() {
"eyJraWQiOiJBSURPRkZDTzJDM05EUVBGQUJDVEFDT1VDU1ZZQUdTR09ZUEJNVU5KS1FEUVFBQUEyTVE2USIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIwMDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIiLCJhdF9oYXNoIjoiUlZfdkZKZnFhdDBGMmFZdHVQUlNlZyIsImF1ZCI6ImNvbS5leGFtcGxlLmFwcGxpbmUud2ViIiwiYXV0aF90aW1lIjoxNjA1MzcxNTU5LCJpc3MiOiJodHRwczovL2lkLmFwcGxlLmNvbSIsImV4cCI6MTYwNTM3NTE1OSwiaWF0IjoxNjA1MzcxNTU5LCJub25jZSI6Ijc5NDc5NTg4MzA1NDQ2OTQzNiIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfQ.VkmD8KcTtCmN65DPwhAPoOeRuXmsLqnm1z8pWa_qHG3xD2LBJgj9YOZPUKseOlfrOz5e5JgIR1qPdWiL2QFuyjCQZ0PSG0hV1xtQ_yYbVHeqLaID0AgcV8Hxldg9hFvF_jvM8G_mo0S9-D8gOR4kbQ";
sut.appleLogin(createAppleLoginCommand(idToken, "홍길동"));

then(memberRepository.findAll()).hasSize(1);
then(memberRepository.findAll())
.hasSize(1)
.extracting("name", "email")
.containsExactly(tuple("홍길동", "[email protected]"));
then(socialAccountRepository.findAll())
.hasSize(1)
.extracting("oauthId", "socialProvider")
Expand All @@ -60,7 +88,7 @@ class whenMemberFound {
@Test
@DisplayName("토큰을 발행한다.")
void shouldIssueToken() {
final Member member = memberRepository.save(Member.create());
final Member member = memberRepository.save(Member.create("홍길동", "[email protected]"));
kimyu0218 marked this conversation as resolved.
Show resolved Hide resolved
final String oauthId = "001234567890123456789012";
socialAccountRepository.save(createSocialAccount(member, oauthId, SocialProvider.APPLE));

Expand All @@ -74,16 +102,58 @@ void shouldIssueToken() {
}
}

private SocialAccount createSocialAccount(
final Member member, final String oauthId, final SocialProvider socialProvider) {
return SocialAccountFixture.builder()
.member(member)
.oauthId(oauthId)
.socialProvider(socialProvider)
.build();
}
@Nested
@DisplayName("kakaoLogin 메소드는")
class kakaoLogin {

@BeforeEach
void setUp() {
given(kakaoAuthClient.requestToken(any()))
.willReturn(new KakaoTokenResponse("bearer", "access token", 1, "refresh token", 1));
given(kakaoClient.requestUserInfo("Bearer access token"))
.willReturn(new KakaoUserInfoResponse(1L, new KakaoAccount("홍길동", "[email protected]")));
kimyu0218 marked this conversation as resolved.
Show resolved Hide resolved
}

private AppleLoginCommand createAppleLoginCommand(String idToken, String name) {
return new AppleLoginCommand(idToken, name);
@Nested
@DisplayName("id값과 일치하는 유저를 찾지 못하면")
class whenMemberNotFound {

@Test
@DisplayName("회원가입을 진행한다.")
void shouldSignIn() {
sut.kakaoLogin(createKakaoLoginCommand("카카오 인가 코드", "카카오 로그인 리다이렉트 URI"));

assertAll(
() ->
then(memberRepository.findAll())
.hasSize(1)
.extracting("name", "email")
.containsExactly(tuple("홍길동", "[email protected]")),
() ->
then(socialAccountRepository.findAll())
.hasSize(1)
.extracting("oauthId", "socialProvider")
.containsExactlyInAnyOrder(tuple("1", SocialProvider.KAKAO)));
}
}

@Nested
@DisplayName("id값과 일치하는 유저를 찾으면")
class whenMemberFound {

@Test
@DisplayName("로그인을 진행한다.")
void shouldSignIn() {
final Member member = memberRepository.save(Member.create("홍길동", "[email protected]"));
final String oauthId = "1";
socialAccountRepository.save(createSocialAccount(member, oauthId, SocialProvider.KAKAO));

sut.kakaoLogin(createKakaoLoginCommand("카카오 인가 코드", "카카오 로그인 리다이렉트 URI"));

assertAll(
() -> then(memberRepository.findAll()).hasSize(1),
() -> then(socialAccountRepository.findAll()).hasSize(1));
}
}
}
}
Loading