-
Notifications
You must be signed in to change notification settings - Fork 2
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/#462 닉네임 변경 API 구현 #470
base: main
Are you sure you want to change the base?
Changes from all commits
9ef006e
95bd4c7
ee8bc8a
3d14050
ded41a5
d52d13c
9bfb040
621a625
4685592
5a64990
2847c8d
05fdcf1
514c475
f0e54f9
04af872
3d46182
72ea86a
d9115bf
21358a7
8ca6f07
9c6fc11
21f4d60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package shook.shook.auth.application; | ||
|
||
import io.jsonwebtoken.Claims; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import shook.shook.auth.application.dto.ReissueAccessTokenResponse; | ||
import shook.shook.auth.application.dto.TokenPair; | ||
import shook.shook.auth.repository.InMemoryTokenPairRepository; | ||
|
||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor | ||
@Service | ||
public class TokenService { | ||
|
||
public static final String TOKEN_PREFIX = "Bearer "; | ||
|
||
private final TokenProvider tokenProvider; | ||
private final InMemoryTokenPairRepository inMemoryTokenPairRepository; | ||
|
||
public String createAccessToken(final Long memberId, final String nickname) { | ||
return tokenProvider.createAccessToken(memberId, nickname); | ||
} | ||
|
||
public String createRefreshToken(final Long memberId, final String nickname) { | ||
return tokenProvider.createRefreshToken(memberId, nickname); | ||
} | ||
|
||
public ReissueAccessTokenResponse reissueAccessTokenByRefreshToken(final String refreshToken, | ||
final String accessToken) { | ||
inMemoryTokenPairRepository.validateTokenPair(refreshToken, accessToken); | ||
final Claims claims = tokenProvider.parseClaims(refreshToken); | ||
final Long memberId = claims.get("memberId", Long.class); | ||
final String nickname = claims.get("nickname", String.class); | ||
|
||
final String reissuedAccessToken = tokenProvider.createAccessToken(memberId, nickname); | ||
inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, reissuedAccessToken); | ||
|
||
return new ReissueAccessTokenResponse(reissuedAccessToken); | ||
} | ||
|
||
public TokenPair updateWithNewTokenPair(final Long memberId, final String nickname) { | ||
final String accessToken = createAccessToken(memberId, nickname); | ||
final String refreshToken = createRefreshToken(memberId, nickname); | ||
inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, accessToken); | ||
|
||
return new TokenPair(refreshToken, accessToken); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,15 @@ | ||
package shook.shook.auth.ui; | ||
|
||
import static shook.shook.auth.application.TokenService.TOKEN_PREFIX; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.CookieValue; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestHeader; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import shook.shook.auth.application.AuthService; | ||
import shook.shook.auth.application.TokenService; | ||
import shook.shook.auth.application.dto.ReissueAccessTokenResponse; | ||
import shook.shook.auth.exception.AuthorizationException; | ||
import shook.shook.auth.ui.openapi.AccessTokenReissueApi; | ||
|
@@ -18,22 +20,29 @@ public class AccessTokenReissueController implements AccessTokenReissueApi { | |
|
||
private static final String EMPTY_REFRESH_TOKEN = "none"; | ||
private static final String REFRESH_TOKEN_KEY = "refreshToken"; | ||
private static final String TOKEN_PREFIX = "Bearer "; | ||
|
||
private final AuthService authService; | ||
private final TokenService tokenService; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
@PostMapping("/reissue") | ||
public ResponseEntity<ReissueAccessTokenResponse> reissueAccessToken( | ||
@CookieValue(value = REFRESH_TOKEN_KEY, defaultValue = EMPTY_REFRESH_TOKEN) final String refreshToken, | ||
@RequestHeader(HttpHeaders.AUTHORIZATION) final String authorization | ||
) { | ||
validateRefreshToken(refreshToken); | ||
final String accessToken = extractAccessToken(authorization); | ||
final ReissueAccessTokenResponse response = tokenService.reissueAccessTokenByRefreshToken(refreshToken, | ||
accessToken); | ||
|
||
return ResponseEntity.ok(response); | ||
} | ||
|
||
private void validateRefreshToken(final String refreshToken) { | ||
if (refreshToken.equals(EMPTY_REFRESH_TOKEN)) { | ||
throw new AuthorizationException.RefreshTokenNotFoundException(); | ||
} | ||
final String accessToken = authorization.split(TOKEN_PREFIX)[1]; | ||
final ReissueAccessTokenResponse response = | ||
authService.reissueAccessTokenByRefreshToken(refreshToken, accessToken); | ||
} | ||
|
||
return ResponseEntity.ok(response); | ||
private String extractAccessToken(final String authorization) { | ||
return authorization.substring(TOKEN_PREFIX.length()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,15 @@ | |
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import shook.shook.auth.application.TokenProvider; | ||
import shook.shook.auth.exception.AuthorizationException; | ||
import shook.shook.auth.ui.argumentresolver.MemberInfo; | ||
import shook.shook.auth.repository.InMemoryTokenPairRepository; | ||
import shook.shook.member.application.dto.NicknameUpdateRequest; | ||
import shook.shook.member.domain.Email; | ||
import shook.shook.member.domain.Member; | ||
import shook.shook.member.domain.Nickname; | ||
|
@@ -27,6 +30,8 @@ public class MemberService { | |
private final MemberRepository memberRepository; | ||
private final KillingPartCommentRepository commentRepository; | ||
private final KillingPartLikeRepository likeRepository; | ||
private final TokenProvider tokenProvider; | ||
private final InMemoryTokenPairRepository inMemoryTokenPairRepository; | ||
|
||
@Transactional | ||
public Member register(final String email) { | ||
|
@@ -37,6 +42,7 @@ public Member register(final String email) { | |
final Member newMember = new Member(email, BASIC_NICKNAME); | ||
final Member savedMember = memberRepository.save(newMember); | ||
savedMember.updateNickname(savedMember.getNickname() + savedMember.getId()); | ||
|
||
return savedMember; | ||
} | ||
|
||
|
@@ -54,20 +60,27 @@ public Member findByIdAndNicknameThrowIfNotExist(final Long id, final Nickname n | |
} | ||
|
||
@Transactional | ||
public void deleteById(final Long id, final MemberInfo memberInfo) { | ||
final long requestMemberId = memberInfo.getMemberId(); | ||
final Member requestMember = findById(requestMemberId); | ||
final Member targetMember = findById(id); | ||
validateMemberAuthentication(requestMember, targetMember); | ||
|
||
final List<KillingPartLike> membersExistLikes = likeRepository.findAllByMemberAndIsDeleted( | ||
targetMember, | ||
false | ||
); | ||
public void deleteById(final Long id, final Long requestMemberId) { | ||
final Member member = getMemberIfValidRequest(id, requestMemberId); | ||
|
||
final List<KillingPartLike> membersExistLikes = likeRepository.findAllByMemberAndIsDeleted(member, false); | ||
|
||
membersExistLikes.forEach(KillingPartLike::updateDeletion); | ||
commentRepository.deleteAllByMember(targetMember); | ||
memberRepository.delete(targetMember); | ||
commentRepository.deleteAllByMember(member); | ||
memberRepository.delete(member); | ||
} | ||
|
||
private Member getMemberIfValidRequest(final Long memberId, final Long requestMemberId) { | ||
if (Objects.equals(memberId, requestMemberId)) { | ||
return findById(memberId); | ||
} | ||
|
||
throw new AuthorizationException.UnauthenticatedException( | ||
Map.of( | ||
"tokenMemberId", String.valueOf(requestMemberId), | ||
"pathMemberId", String.valueOf(memberId) | ||
) | ||
); | ||
} | ||
|
||
private Member findById(final Long id) { | ||
|
@@ -77,14 +90,27 @@ private Member findById(final Long id) { | |
)); | ||
} | ||
|
||
private void validateMemberAuthentication(final Member requestMember, | ||
final Member targetMember) { | ||
if (!requestMember.equals(targetMember)) { | ||
throw new AuthorizationException.UnauthenticatedException( | ||
Map.of( | ||
"tokenMemberId", String.valueOf(requestMember.getId()), | ||
"pathMemberId", String.valueOf(targetMember.getId()) | ||
) | ||
@Transactional | ||
public boolean updateNickname(final Long memberId, final Long requestMemberId, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 위의 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 memberInfo 의 id 만 쓰이고 있는 상태라 id 만 넘겨주는 걸로 통일했습니다 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
final NicknameUpdateRequest request) { | ||
final Member member = getMemberIfValidRequest(memberId, requestMemberId); | ||
final Nickname nickname = new Nickname(request.getNickname()); | ||
|
||
if (member.hasSameNickname(nickname)) { | ||
return false; | ||
} | ||
|
||
validateDuplicateNickname(nickname); | ||
member.updateNickname(nickname.getValue()); | ||
|
||
return true; | ||
} | ||
|
||
private void validateDuplicateNickname(final Nickname nickname) { | ||
final boolean isDuplicated = memberRepository.existsMemberByNickname(nickname); | ||
if (isDuplicated) { | ||
throw new MemberException.ExistNicknameException( | ||
Map.of("Nickname", nickname.getValue()) | ||
); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package shook.shook.member.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotBlank; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Schema(description = "닉네임 변경 요청") | ||
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) | ||
@AllArgsConstructor | ||
@Getter | ||
public class NicknameUpdateRequest { | ||
|
||
@Schema(description = "닉네임", example = "shookshook") | ||
@NotBlank | ||
private String nickname; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,14 @@ public void updateNickname(final String newNickName) { | |
this.nickname = new Nickname(newNickName); | ||
} | ||
|
||
public void updateNickname(final Nickname newNickname) { | ||
this.nickname = newNickname; | ||
} | ||
Comment on lines
51
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 의견)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
public boolean hasSameNickname(final Nickname nickname) { | ||
return nickname.equals(this.nickname); | ||
} | ||
|
||
public String getEmail() { | ||
return email.getValue(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍👍