Skip to content

Commit

Permalink
merge: 멤버관리 api 추가
Browse files Browse the repository at this point in the history
Feature/#83 멤버관리 api 추가
  • Loading branch information
hong-sile authored Aug 10, 2024
2 parents b92aae5 + b040e5f commit c658b4a
Show file tree
Hide file tree
Showing 23 changed files with 426 additions and 15 deletions.
20 changes: 20 additions & 0 deletions src/main/java/play/pluv/config/ArgumentResolverConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package play.pluv.config;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import play.pluv.login.application.MemberIdArgumentResolver;

@Configuration
@RequiredArgsConstructor
public class ArgumentResolverConfig implements WebMvcConfigurer {

private final MemberIdArgumentResolver memberIdArgumentResolver;

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(memberIdArgumentResolver);
}
}
12 changes: 12 additions & 0 deletions src/main/java/play/pluv/login/application/LoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import play.pluv.member.domain.Member;
import play.pluv.oauth.application.SocialLoginClientComposite;
import play.pluv.oauth.domain.OAuthMemberInfo;
Expand All @@ -15,6 +16,7 @@ public class LoginService {
private final RegisterUpdater registerUpdater;
private final SocialLoginClientComposite socialLoginClientComposite;

@Transactional
public Long createToken(final MusicStreaming serverType, final String key) {
final OAuthMemberInfo memberInfo = socialLoginClientComposite
.fetchMemberInfo(serverType, key);
Expand All @@ -24,4 +26,14 @@ public Long createToken(final MusicStreaming serverType, final String key) {

return member.getId();
}

@Transactional
public void addOtherLoginWay(
final MusicStreaming serverType, final Long memberId, final String key
) {
final OAuthMemberInfo memberInfo = socialLoginClientComposite
.fetchMemberInfo(serverType, key);

registerUpdater.addOtherLoginSource(memberId, memberInfo);
}
}
18 changes: 17 additions & 1 deletion src/main/java/play/pluv/login/application/RegisterUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import play.pluv.member.application.NickNameGenerator;
import play.pluv.member.domain.Member;
import play.pluv.member.domain.NickName;
import play.pluv.member.domain.repository.MemberRepository;
import play.pluv.oauth.domain.OAuthMemberInfo;

@Component
Expand All @@ -17,6 +18,7 @@ public class RegisterUpdater {

private final SocialLoginIdRepository socialLoginIdRepository;
private final NickNameGenerator nickNameGenerator;
private final MemberRepository memberRepository;

public Member registerNewMember(final OAuthMemberInfo memberInfo) {
final NickName nickName = nickNameGenerator.generateNickName();
Expand All @@ -28,6 +30,20 @@ public Member registerNewMember(final OAuthMemberInfo memberInfo) {
.build();
socialLoginIdRepository.save(loginId);

return member;
return memberRepository.save(member);
}

public void addOtherLoginSource(final Long memberId, final OAuthMemberInfo memberInfo) {
if (socialLoginIdRepository.existsByOauthMemberInfo(memberInfo)) {
return;
}
final Member member = memberRepository.readById(memberId);

final SocialLoginId loginId = SocialLoginId.builder()
.oauthMemberInfo(memberInfo)
.member(member)
.build();

socialLoginIdRepository.save(loginId);
}
}
17 changes: 17 additions & 0 deletions src/main/java/play/pluv/login/controller/LoginController.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import play.pluv.login.application.JwtProvider;
import play.pluv.login.application.LoginService;
import play.pluv.login.application.dto.GoogleLoginRequest;
import play.pluv.login.application.dto.JwtMemberId;
import play.pluv.login.application.dto.LoginResponse;
import play.pluv.login.application.dto.SpotifyLoginRequest;

Expand Down Expand Up @@ -39,4 +40,20 @@ public BaseResponse<LoginResponse> loginGoogle(
final var loginResponse = new LoginResponse(jwtProvider.createAccessTokenWith(memberId));
return BaseResponse.ok(loginResponse);
}

@PostMapping("/login/google/add")
public BaseResponse<String> addGoogleLoginWay(
@Valid @RequestBody final GoogleLoginRequest loginRequest, final JwtMemberId jwtMemberId
) {
loginService.addOtherLoginWay(YOUTUBE, jwtMemberId.memberId(), loginRequest.idToken());
return BaseResponse.ok("");
}

@PostMapping("/login/spotify/add")
public BaseResponse<String> addSpotifyLoginWay(
@Valid @RequestBody final SpotifyLoginRequest loginRequest, final JwtMemberId jwtMemberId
) {
loginService.addOtherLoginWay(SPOTIFY, jwtMemberId.memberId(), loginRequest.accessToken());
return BaseResponse.ok("");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface SocialLoginIdRepository extends JpaRepository<SocialLoginId, Lo
where s.oauthMemberInfo = :oauthMemberInfo
""")
Optional<SocialLoginId> findByOAuthMemberInfo(final OAuthMemberInfo oauthMemberInfo);

Boolean existsByOauthMemberInfo(final OAuthMemberInfo oauthMemberInfo);
}
23 changes: 23 additions & 0 deletions src/main/java/play/pluv/member/application/MemberService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package play.pluv.member.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import play.pluv.member.domain.NickName;

@Service
@RequiredArgsConstructor
public class MemberService {

private final MemberUpdater memberUpdater;

@Transactional
public void updateNickname(final Long memberId, final String nickName) {
memberUpdater.updateNickName(memberId, new NickName(nickName));
}

@Transactional
public void unregister(final Long memberId) {
memberUpdater.unregister(memberId);
}
}
24 changes: 24 additions & 0 deletions src/main/java/play/pluv/member/application/MemberUpdater.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package play.pluv.member.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import play.pluv.member.domain.NickName;
import play.pluv.member.domain.repository.MemberRepository;

@Component
@RequiredArgsConstructor
@Transactional
public class MemberUpdater {

private final MemberRepository memberRepository;

public void updateNickName(final Long memberId, final NickName nickName) {
memberRepository.readById(memberId)
.updateNickName(nickName);
}

public void unregister(final Long memberId) {
memberRepository.deleteById(memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package play.pluv.member.application.dto;

public record UpdateNickNameRequest(String nickName) {

}
34 changes: 34 additions & 0 deletions src/main/java/play/pluv/member/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package play.pluv.member.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import play.pluv.base.BaseResponse;
import play.pluv.login.application.dto.JwtMemberId;
import play.pluv.member.application.MemberService;
import play.pluv.member.application.dto.UpdateNickNameRequest;

@RequiredArgsConstructor
@RestController
public class MemberController {

private final MemberService memberService;

@PostMapping("/member/nickname")
public ResponseEntity<BaseResponse<String>> updateNickName(
@Valid @RequestBody final UpdateNickNameRequest updateNickNameRequest,
final JwtMemberId jwtMemberId
) {
memberService.updateNickname(jwtMemberId.memberId(), updateNickNameRequest.nickName());
return ResponseEntity.ok(BaseResponse.ok(""));
}

@PostMapping("/member/unregister")
public ResponseEntity<BaseResponse<String>> unregister(final JwtMemberId jwtMemberId) {
memberService.unregister(jwtMemberId.memberId());
return ResponseEntity.ok(BaseResponse.ok(""));
}
}
14 changes: 13 additions & 1 deletion src/main/java/play/pluv/member/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package play.pluv.member.domain;

import static jakarta.persistence.GenerationType.IDENTITY;
import static java.lang.Boolean.FALSE;
import static lombok.AccessLevel.PROTECTED;

import jakarta.persistence.Embedded;
Expand All @@ -9,19 +10,30 @@
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

@Entity
@NoArgsConstructor(access = PROTECTED)
@Getter
@SQLDelete(sql = "UPDATE member SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
public class Member {

@Id
@GeneratedValue(strategy = IDENTITY)
@Getter
private Long id;

@Embedded
private NickName nickName;

private Boolean deleted = FALSE;

public Member(final NickName nickName) {
this.nickName = nickName;
}

public void updateNickName(final NickName nickName) {
this.nickName = nickName;
}
}
7 changes: 0 additions & 7 deletions src/main/java/play/pluv/member/domain/MemberRepository.java

This file was deleted.

2 changes: 2 additions & 0 deletions src/main/java/play/pluv/member/domain/NickName.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import static play.pluv.member.exception.MemberExceptionType.NICK_NAME_LENGTH_IS_OVER;

import jakarta.persistence.Embeddable;
import lombok.Getter;
import lombok.NoArgsConstructor;
import play.pluv.member.exception.MemberException;

@Embeddable
@NoArgsConstructor(access = PROTECTED)
@Getter
public class NickName {

private static final int MAX_LENGTH = 10;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package play.pluv.member.domain.repository;

import static play.pluv.member.exception.MemberExceptionType.MEMBER_NOT_FOUND;

import org.springframework.data.jpa.repository.JpaRepository;
import play.pluv.member.domain.Member;
import play.pluv.member.exception.MemberException;

public interface MemberRepository extends JpaRepository<Member, Long> {

default Member readById(final Long id) {
return findById(id).orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package play.pluv.member.exception;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -9,7 +12,8 @@
@RequiredArgsConstructor
public enum MemberExceptionType implements BaseExceptionType {

NICK_NAME_LENGTH_IS_OVER(HttpStatus.BAD_REQUEST, "닉네임 길이를 초과하셨습니다.");
NICK_NAME_LENGTH_IS_OVER(BAD_REQUEST, "닉네임 길이를 초과하셨습니다."),
MEMBER_NOT_FOUND(NOT_FOUND, "멤버를 찾을 수 없습니다");

private final HttpStatus httpStatus;
private final String message;
Expand Down
59 changes: 59 additions & 0 deletions src/test/java/play/pluv/api/LoginApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
Expand Down Expand Up @@ -30,6 +31,7 @@ public class LoginApiTest extends ApiTest {
final String requestBody = objectMapper.writeValueAsString(loginRequest);

when(loginService.createToken(SPOTIFY, "accessToken")).thenReturn(2L);
setCreateToken("accessToken", 2L);

mockMvc.perform(post("/login/spotify")
.contentType(APPLICATION_JSON_VALUE)
Expand All @@ -54,6 +56,7 @@ public class LoginApiTest extends ApiTest {
final String requestBody = objectMapper.writeValueAsString(loginRequest);

when(loginService.createToken(YOUTUBE, "idToken")).thenReturn(2L);
setCreateToken("accessToken", 2L);

mockMvc.perform(post("/login/google")
.contentType(APPLICATION_JSON_VALUE)
Expand All @@ -72,4 +75,60 @@ public class LoginApiTest extends ApiTest {
)
));
}

@Test
void 구글_소셜_로그인_방법을_추가한다() throws Exception {
final Long memberId = 10L;
final String token = "access Token";
final GoogleLoginRequest loginRequest = new GoogleLoginRequest("idToken");

final String requestBody = objectMapper.writeValueAsString(loginRequest);
setAccessToken(token, memberId);

mockMvc.perform(post("/login/google/add")
.contentType(APPLICATION_JSON_VALUE)
.content(requestBody)
.header(AUTHORIZATION, "Bearer " + token))
.andExpect(status().isOk())
.andDo(document("google-login-add",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestFields(
fieldWithPath("idToken").type(STRING).description("구글의 accessToken")
),
responseFields(
fieldWithPath("code").type(NUMBER).description("상태 코드"),
fieldWithPath("msg").type(STRING).description("상태 코드에 해당하는 메시지"),
fieldWithPath("data").type(STRING).description("")
)
));
}

@Test
void 스포티파이_소셜_로그인_방법을_추가한다() throws Exception {
final String token = "access Token";
final Long memberId = 10L;
final SpotifyLoginRequest loginRequest = new SpotifyLoginRequest("accessToken");

final String requestBody = objectMapper.writeValueAsString(loginRequest);
setAccessToken(token, memberId);

mockMvc.perform(post("/login/spotify/add")
.contentType(APPLICATION_JSON_VALUE)
.content(requestBody)
.header(AUTHORIZATION, "Bearer " + token))
.andExpect(status().isOk())
.andDo(document("spotify-login-add",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestFields(
fieldWithPath("accessToken").type(STRING).description("스포티파이의 accessToken")
),
responseFields(
fieldWithPath("code").type(NUMBER).description("상태 코드"),
fieldWithPath("msg").type(STRING).description("상태 코드에 해당하는 메시지"),
fieldWithPath("data").type(STRING).description("")
)
));
}
}
Loading

0 comments on commit c658b4a

Please sign in to comment.