Skip to content

Commit d25eb0b

Browse files
authored
Merge branch 'dev' into feat/#38
2 parents 9b1b408 + 3ea0a19 commit d25eb0b

File tree

48 files changed

+1624
-54
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1624
-54
lines changed

.github/workflows/backend-server-test.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
run: chmod +x gradlew
2828

2929
- name: 🧪 Spring Boot Test
30-
run: ./gradlew clean test
30+
run: ./gradlew clean test -i
3131

3232
- name: Add coverage to PR
3333
id: jacoco
@@ -37,4 +37,4 @@ jobs:
3737
paths: ${{ github.workspace }}/**/reports/jacoco/test/jacocoTestReport.xml
3838
token: ${{ secrets.GITHUB_TOKEN }}
3939
min-coverage-overall: 80
40-
min-coverage-changed-files: 80
40+
min-coverage-changed-files: 80

application/application-common/build.gradle

-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ java {
66

77
dependencies {
88
api('org.springframework.boot:spring-boot-starter-web')
9-
api('org.springframework.boot:spring-boot-starter-test')
109
}

application/wypl-core/build.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@ dependencies {
1616
implementation project(':domain:jpamongo-review-domain')
1717
implementation project(':domain:jpa-calendar-domain')
1818
implementation project(':domain:jpa-member-domain')
19+
implementation project(':domain:auth-domain')
1920
implementation project(':domain:redis-embedded-domain')
2021
implementation project(':common')
2122

2223
implementation 'org.springframework.boot:spring-boot-starter-actuator'
24+
25+
/* API Docs */
26+
testImplementation("com.epages:restdocs-api-spec-mockmvc:0.18.4")
27+
28+
/* Test */
29+
testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
2330
}

application/wypl-core/src/main/java/com/wypl/wyplcore/auth/AuthMemberServiceImpl.java

-36
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.wypl.wyplcore.auth.controller;
2+
3+
import org.springframework.web.bind.annotation.DeleteMapping;
4+
import org.springframework.web.bind.annotation.PathVariable;
5+
import org.springframework.web.bind.annotation.PostMapping;
6+
import org.springframework.web.bind.annotation.PutMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RequestParam;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
import com.wypl.applicationcommon.WyplResponseEntity;
12+
import com.wypl.googleoauthclient.annotation.Authenticated;
13+
import com.wypl.googleoauthclient.domain.AuthMember;
14+
import com.wypl.wyplcore.auth.data.response.AuthTokensResponse;
15+
import com.wypl.wyplcore.auth.service.AuthServiceImpl;
16+
17+
import lombok.RequiredArgsConstructor;
18+
19+
@RequiredArgsConstructor
20+
@RequestMapping("/auth")
21+
@RestController
22+
public class AuthController {
23+
private final AuthServiceImpl authService;
24+
25+
@PostMapping("/v1/sign-in/{provider}")
26+
public WyplResponseEntity<AuthTokensResponse> signIn(
27+
@PathVariable("provider") String provider,
28+
@RequestParam("code") String code
29+
) {
30+
AuthTokensResponse response = authService.generateToken(provider, code);
31+
return WyplResponseEntity.ok(response, "로그인에 성공하였습니다.");
32+
}
33+
34+
@PutMapping("/v1/reissue")
35+
public WyplResponseEntity<AuthTokensResponse> reissue(
36+
@RequestParam("access_token") String accessToken,
37+
@RequestParam("refresh_token") String refreshToken
38+
) {
39+
AuthTokensResponse response = authService.reissueToken(accessToken, refreshToken);
40+
return WyplResponseEntity.created(response, "토큰 재발급에 성공하였습니다.");
41+
}
42+
43+
@DeleteMapping("/v1/logout")
44+
public WyplResponseEntity<Void> logout(
45+
@Authenticated AuthMember authMember
46+
) {
47+
authService.logout(authMember);
48+
return WyplResponseEntity.ok("로그아웃에 성공하였습니다.");
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.wypl.wyplcore.auth.data.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.wypl.googleoauthclient.data.response.GoogleTokenResponse;
5+
6+
import lombok.Builder;
7+
8+
@Builder
9+
public record AuthTokensResponse(
10+
@JsonProperty("member_id")
11+
long memberId,
12+
@JsonProperty("access_token")
13+
String accessToken,
14+
@JsonProperty("refresh_token")
15+
String refreshToken
16+
) {
17+
public static AuthTokensResponse of(long memberId, GoogleTokenResponse googleTokenResponse) {
18+
return AuthTokensResponse.builder()
19+
.memberId(memberId)
20+
.accessToken(googleTokenResponse.accessToken())
21+
.refreshToken(googleTokenResponse.refreshToken())
22+
.build();
23+
}
24+
25+
public static AuthTokensResponse of(String accessToken, String refreshToken) {
26+
return AuthTokensResponse.builder()
27+
.accessToken(accessToken)
28+
.refreshToken(refreshToken)
29+
.build();
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.wypl.wyplcore.auth.service;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import com.wypl.authdomain.auth.service.AuthDomainServiceImpl;
6+
import com.wypl.googleoauthclient.GoogleOAuthClient;
7+
import com.wypl.googleoauthclient.data.response.GoogleTokenValidationResponse;
8+
import com.wypl.googleoauthclient.domain.AuthMember;
9+
import com.wypl.googleoauthclient.exception.GoogleOAuthErrorCode;
10+
import com.wypl.googleoauthclient.exception.GoogleOAuthException;
11+
import com.wypl.googleoauthclient.service.AuthMemberService;
12+
import com.wypl.jpamemberdomain.member.domain.SocialMember;
13+
import com.wypl.jpamemberdomain.member.repository.SocialMemberRepository;
14+
import com.wypl.jpamemberdomain.member.utils.SocialMemberRepositoryUtils;
15+
16+
import lombok.RequiredArgsConstructor;
17+
18+
@Component
19+
@RequiredArgsConstructor
20+
public class AuthMemberServiceImpl implements AuthMemberService {
21+
private final GoogleOAuthClient googleOAuthClient;
22+
private final SocialMemberRepository socialMemberRepository;
23+
private final AuthDomainServiceImpl authDomainService;
24+
25+
@Override
26+
public AuthMember getValidatedMemberId(String accessToken) {
27+
GoogleTokenValidationResponse response = getGoogleTokenValidationResponse(accessToken);
28+
29+
SocialMember socialMember = SocialMemberRepositoryUtils.getSocialMember(
30+
socialMemberRepository,
31+
response.userId());
32+
33+
return AuthMember.of(socialMember.getId(), accessToken);
34+
}
35+
36+
private GoogleTokenValidationResponse getGoogleTokenValidationResponse(String accessToken) {
37+
try {
38+
return googleOAuthClient.validateToken(accessToken);
39+
} catch (GoogleOAuthException e) {
40+
if (authDomainService.checkExistsToken(accessToken)) {
41+
throw new GoogleOAuthException(GoogleOAuthErrorCode.REFRESH_TOKEN);
42+
}
43+
throw new GoogleOAuthException(GoogleOAuthErrorCode.INVALID_TOKEN);
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.wypl.wyplcore.auth.service;
2+
3+
import java.time.LocalDate;
4+
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.transaction.annotation.Transactional;
7+
8+
import com.wypl.authdomain.auth.service.AuthDomainServiceImpl;
9+
import com.wypl.googleoauthclient.GoogleOAuthClient;
10+
import com.wypl.googleoauthclient.data.response.GoogleTokenResponse;
11+
import com.wypl.googleoauthclient.data.response.GoogleUserInfoResponse;
12+
import com.wypl.googleoauthclient.domain.AuthMember;
13+
import com.wypl.googleoauthclient.exception.GoogleOAuthErrorCode;
14+
import com.wypl.googleoauthclient.exception.GoogleOAuthException;
15+
import com.wypl.jpamemberdomain.member.OauthProvider;
16+
import com.wypl.jpamemberdomain.member.data.MemberSaveDto;
17+
import com.wypl.jpamemberdomain.member.data.SocialMemberSaveDto;
18+
import com.wypl.jpamemberdomain.member.repository.SocialMemberRepository;
19+
import com.wypl.jpamemberdomain.member.utils.SocialMemberRepositoryUtils;
20+
import com.wypl.wyplcore.auth.data.response.AuthTokensResponse;
21+
import com.wypl.wyplcore.member.service.MemberServiceImpl;
22+
23+
import lombok.RequiredArgsConstructor;
24+
25+
@Transactional(readOnly = true)
26+
@RequiredArgsConstructor
27+
@Service
28+
public class AuthServiceImpl {
29+
private final GoogleOAuthClient googleOAuthClient;
30+
private final SocialMemberRepository socialMemberRepository;
31+
private final AuthDomainServiceImpl authDomainService;
32+
private final MemberServiceImpl memberService;
33+
34+
@Transactional
35+
public AuthTokensResponse generateToken(final String provider, final String code) {
36+
GoogleTokenResponse googleTokenResponse = googleOAuthClient.fetchGoogleOAuthToken(code);
37+
38+
GoogleUserInfoResponse googleUserInfoResponse = googleOAuthClient.fetchUserInfo(
39+
googleTokenResponse.accessToken());
40+
41+
long memberId = findMemberIdAfterSaveMember(googleTokenResponse.accessToken(), googleUserInfoResponse);
42+
43+
authDomainService.saveToken(googleTokenResponse.accessToken(), googleTokenResponse.refreshToken());
44+
45+
return AuthTokensResponse.of(memberId, googleTokenResponse);
46+
}
47+
48+
@Transactional
49+
public AuthTokensResponse reissueToken(final String accessToken, final String refreshToken) {
50+
if (isInvalidRefreshToken(accessToken, refreshToken)) {
51+
throw new GoogleOAuthException(GoogleOAuthErrorCode.NOT_AUTHORIZATION_MEMBER);
52+
}
53+
54+
GoogleTokenResponse googleTokenResponse = googleOAuthClient.fetchRefreshGoogleOAuthToken(refreshToken);
55+
56+
authDomainService.deleteToken(accessToken);
57+
authDomainService.saveToken(googleTokenResponse.accessToken(), refreshToken);
58+
59+
return AuthTokensResponse.of(googleTokenResponse.accessToken(), refreshToken);
60+
}
61+
62+
@Transactional
63+
public void logout(AuthMember authMember) {
64+
deleteToken(authMember);
65+
}
66+
67+
@Transactional
68+
public void quitMember(AuthMember authMember) {
69+
// Todo : 회원 탈퇴 로직 논의
70+
deleteToken(authMember);
71+
memberService.deleteMember(authMember);
72+
}
73+
74+
private void deleteToken(AuthMember authMember) {
75+
authDomainService.deleteToken(authMember.accessToken());
76+
}
77+
78+
private boolean isInvalidRefreshToken(String accessToken, String refreshToken) {
79+
return refreshToken.isEmpty() || !refreshToken.equals(authDomainService.getRefreshToken(accessToken));
80+
}
81+
82+
// todo:
83+
private long findMemberIdAfterSaveMember(String accessToken, GoogleUserInfoResponse googleUserInfoResponse) {
84+
if (isNewMember(googleUserInfoResponse)) {
85+
LocalDate birthday = googleOAuthClient.fetchBirthday(accessToken);
86+
87+
MemberSaveDto memberSaveDto = MemberSaveDto.builder()
88+
.email(googleUserInfoResponse.email())
89+
.birthday(birthday)
90+
.nickname(googleUserInfoResponse.name())
91+
.profileImage(googleUserInfoResponse.picture())
92+
.build();
93+
94+
SocialMemberSaveDto socialMemberSaveDto = SocialMemberSaveDto.builder()
95+
.oauthProvider(OauthProvider.GOOGLE)
96+
.oauthId(googleUserInfoResponse.id())
97+
.build();
98+
99+
return authDomainService.saveAuthData(memberSaveDto, socialMemberSaveDto);
100+
}
101+
102+
return SocialMemberRepositoryUtils.getSocialMember(socialMemberRepository, googleUserInfoResponse.id()).getId();
103+
}
104+
105+
// todo:
106+
private boolean isNewMember(GoogleUserInfoResponse googleUserInfoResponse) {
107+
return !socialMemberRepository.existsByOauthProviderAndOauthId(OauthProvider.GOOGLE,
108+
googleUserInfoResponse.id());
109+
}
110+
}
111+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.wypl.wyplcore.member.service;
2+
3+
import org.springframework.stereotype.Service;
4+
import org.springframework.transaction.annotation.Transactional;
5+
6+
import com.wypl.googleoauthclient.domain.AuthMember;
7+
import com.wypl.jpamemberdomain.member.repository.MemberRepository;
8+
9+
import lombok.RequiredArgsConstructor;
10+
11+
@Transactional(readOnly = true)
12+
@RequiredArgsConstructor
13+
@Service
14+
public class MemberServiceImpl {
15+
private final MemberRepository memberRepository;
16+
17+
@Transactional
18+
public void deleteMember(AuthMember authMember) {
19+
memberRepository.deleteById(authMember.id());
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.wypl.wyplcore;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
import com.wypl.WyplCoreApplication;
7+
8+
@SpringBootApplication
9+
public class WyplCoreTestApplication {
10+
public static void main(String[] args) {
11+
SpringApplication.run(WyplCoreApplication.class, args);
12+
}
13+
}

application/wypl-core/src/test/java/com/wypl/wyplcore/auth/AuthMemberServiceImplTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
import com.wypl.googleoauthclient.data.response.GoogleTokenValidationResponse;
1919
import com.wypl.googleoauthclient.domain.AuthMember;
2020
import com.wypl.jpamemberdomain.member.OauthProvider;
21-
import com.wypl.jpamemberdomain.member.SocialMemberRepository;
21+
import com.wypl.jpamemberdomain.member.repository.SocialMemberRepository;
2222
import com.wypl.jpamemberdomain.member.domain.SocialMember;
2323
import com.wypl.jpamemberdomain.member.exception.MemberErrorCode;
2424
import com.wypl.jpamemberdomain.member.exception.MemberException;
25+
import com.wypl.wyplcore.auth.service.AuthMemberServiceImpl;
2526

2627
@ExtendWith(MockitoExtension.class)
2728
class AuthMemberServiceImplTest {

0 commit comments

Comments
 (0)