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/#462 닉네임 변경 API 구현 #470

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9ef006e
feat: 닉네임 변경 API 구현
Cyma-s Sep 26, 2023
95bd4c7
docs: API 명세 추가
Cyma-s Sep 26, 2023
ee8bc8a
style: 개행 추가 및 불필요한 공백 삭제, static import 삭제
Cyma-s Sep 27, 2023
3d14050
refactor: 에러 코드 이름 수정
Cyma-s Sep 27, 2023
ded41a5
refactor: 변수 이름 리팩터링
Cyma-s Sep 27, 2023
d52d13c
style: 헤더 위치 변경
Cyma-s Sep 27, 2023
9bfb040
test: 엣지 케이스를 테스트하도록 변경
Cyma-s Sep 27, 2023
621a625
refactor: member 에게 닉네임이 같은지 물어 보도록 메서드 변경
Cyma-s Sep 27, 2023
4685592
fix: @Valid 어노테이션 추가
Cyma-s Sep 27, 2023
5a64990
feat: 리프레시 토큰이 member id 로만 만들어지도록 수정
Cyma-s Sep 29, 2023
2847c8d
refactor: 닉네임 최소 길이 추가
Cyma-s Sep 29, 2023
05fdcf1
fix: cookie setPath 설정 변경
Cyma-s Sep 29, 2023
514c475
fix: cookie 테스트 수정
Cyma-s Sep 29, 2023
f0e54f9
refactor: 에러코드 문구 수정
Cyma-s Sep 30, 2023
04af872
fix: 닉네임 변경 시 응답을 내려주지 않도록 수정
Cyma-s Oct 3, 2023
3d46182
refactor: 토큰 검증 책임 ReissueController 로 이동
Cyma-s Oct 5, 2023
72ea86a
refactor: 검증 순서 변경
Cyma-s Oct 5, 2023
d9115bf
refactor: delete 메서드도 memberId 를 입력 받도록 변경
Cyma-s Oct 5, 2023
21358a7
refactor: 사용하지 않는 클래스 삭제
Cyma-s Oct 5, 2023
8ca6f07
refactor: 검증 로직 간소화
Cyma-s Oct 5, 2023
9c6fc11
refactor: save 문 삭제
Cyma-s Oct 5, 2023
21f4d60
refactor: TokenProvider, InMemoryTokenPairRepository 의존성 삭제
Cyma-s Oct 6, 2023
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
@@ -1,9 +1,7 @@
package shook.shook.auth.application;

import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import shook.shook.auth.application.dto.ReissueAccessTokenResponse;
import shook.shook.auth.application.dto.TokenPair;
import shook.shook.auth.repository.InMemoryTokenPairRepository;
import shook.shook.member.application.MemberService;
Expand Down Expand Up @@ -34,16 +32,4 @@ public TokenPair oAuthLogin(final String oauthType, final String authorizationCo
inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, accessToken);
return new TokenPair(accessToken, refreshToken);
}

public ReissueAccessTokenResponse reissueAccessTokenByRefreshToken(final String refreshToken,
final String accessToken) {
final Claims claims = tokenProvider.parseClaims(refreshToken);
final Long memberId = claims.get("memberId", Long.class);
final String nickname = claims.get("nickname", String.class);

inMemoryTokenPairRepository.validateTokenPair(refreshToken, accessToken);
final String reissuedAccessToken = tokenProvider.createAccessToken(memberId, nickname);
inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, reissuedAccessToken);
return new ReissueAccessTokenResponse(reissuedAccessToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private String createToken(final long memberId, final String nickname, final lon
claims.put("memberId", memberId);
claims.put("nickname", nickname);
final Date now = new Date();

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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.exception.AuthorizationException;
import shook.shook.auth.repository.InMemoryTokenPairRepository;

@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class TokenService {

public static final String EMPTY_REFRESH_TOKEN = "none";
public static final String TOKEN_PREFIX = "Bearer ";

private final TokenProvider tokenProvider;
private final InMemoryTokenPairRepository inMemoryTokenPairRepository;

Copy link
Collaborator

Choose a reason for hiding this comment

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

현재 TokenService 내부에 TokenProviderInMemoryTokenPairRepository를 가지고 구성이 되어 있는데 MemberService와 AuthService는 TokenService를 의존하는 것이 아닌 TokenProviderInMemoryTokenPairRepository의존하고 있는데 이 부분이 전에 말했던 순환참조 때문이었나요? 그렇지 않으면 TokenService를 의존하는 것이 좋을 것 같은데 어떻게 생각하시나요?

public void validateRefreshToken(final String refreshToken) {
if (refreshToken.equals(EMPTY_REFRESH_TOKEN)) {
throw new AuthorizationException.RefreshTokenNotFoundException();
}
}

public String extractAccessToken(final String authorization) {
return authorization.substring(TOKEN_PREFIX.length());
Copy link
Collaborator

Choose a reason for hiding this comment

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

Bearer {accessToken} 구조의 문자열에서 엑세스 토큰만 추출하는 로직인 것 같은데, 엑세스 토큰이 Bearer로 시작하지 않는 상황에 대해서는 따로 검증하지 않아도 괜찮을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

그리고 만약 authorization 문자열이 Bearer 보다 짧은, 잘못된 값이 들어오면 substring 에서 IndexOutOfBoundException이 발생할 수도 있을 것 같아요!

}

public ReissueAccessTokenResponse reissueAccessTokenByRefreshToken(final String refreshToken,
final String accessToken) {
final Claims claims = tokenProvider.parseClaims(refreshToken);
final Long memberId = claims.get("memberId", Long.class);
final String nickname = claims.get("nickname", String.class);

inMemoryTokenPairRepository.validateTokenPair(refreshToken, accessToken);
Copy link
Collaborator

Choose a reason for hiding this comment

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

tokenProvider.parseClaims 보다 InMemoryTokenPairRepository.validateTokenPair 가 선행되어야 하지 않을까요?

현재 TokenScheduler에 의해 1초마다 스케줄러를 돌려서 만료된 refreshToken을 삭제하고, Repository에 없는 refreshToken으로 재발급 요청이 오면 RefreshTokenNotFoundException 을 발생시키는 것으로 알고있어요.

만약 refreshToken이 만료되었는데, 이를 캐치하지 못하고 먼저 tokenProvider.parseClaimsrefreshToken을 추출한다면 아래와 같은 문제가 생길 것 같아요.


parseClaims가 파라미터로 받은 token의 유효기간이 만료되었다면 TokenException.ExpiredTokenException 을 발생시키고 프론트엔드에게 EXPIRED_TOKEN(1001, "유효기간이 만료된 토큰입니다.") 에러 메세지가 반환됩니다.

그러면 프론트엔드에서는 accessToken이 만료되었으니, refreshToken을 가지고 reissue 요청을 보내야 하는 상황 으로 인식이 될 것 같아요. (저희가 만료된 토큰이 accessToken인지, refreshToken인지에 대해 식별을 안하고 있는 걸로 알고 있습니다)
그런데 만료된 것이 refreshToken이라면 어떻게 될까요? 이 상황에서도 동일하게 유효기간이 만료된 토큰 이라는 에러 메세지가 프론트엔드에게 전달될 것 같아요.
그리고 다음 단계인 InMemoryTokenPairRepository.validateTokenPair까지 흐름이 넘어가지 않고 예외가 발생한 상태로 끝나버리겠죠?

이렇게 되면 결과적으로 "accessToken이 만료되어서 재발급 받으려 하는데, refreshToken도 이미 만료가 된 상황"에 대한 처리가 제대로 이루어지지 않을 것 같다는 생각이 들어요.

베로는 어떻게 생각하시나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

날카로운 지적입니다. validateTokenPair 가 먼저 실행되어야 한다는 것에 100% 동의합니다
추가로 parseClaim 을 할 때, 어떤 토큰이 만료되었는지 로깅하는 건 어떤가요?

final String reissuedAccessToken = tokenProvider.createAccessToken(memberId, nickname);
inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, reissuedAccessToken);

return new ReissueAccessTokenResponse(reissuedAccessToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
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;

@RequiredArgsConstructor
Expand All @@ -18,21 +17,18 @@ 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
) {
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);
tokenService.validateRefreshToken(refreshToken);
final String accessToken = tokenService.extractAccessToken(authorization);
final ReissueAccessTokenResponse response = tokenService.reissueAccessTokenByRefreshToken(refreshToken,
accessToken);
Copy link
Collaborator

@somsom13 somsom13 Oct 3, 2023

Choose a reason for hiding this comment

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

TokenServicevalidateRefreshToken, extractAccessToken 모두 토큰 재발급 용도로 이 controller 메서드 내에서만 사용되는 것 같아요.

reissue 메서드 내에서 validate, extract 를 함께 하도록 하지 않고 validate와 extract를 public으로 두고 controller 에서 따로 호출하도록 하신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

지금은 사라졌지만 닉네임 변경 시 리프레시 토큰을 받도록 하면 다른 곳에서도 검증이 필요해서 public 메서드로 만들어두었습니다!
그런데 생각해보면 현재 리프레시 토큰은 path 가 /reissue 로 고정되어 있기 때문에 private 메서드로 둬도 될 것 같네요 좋은 리뷰 감사합니다 👍🏻


return ResponseEntity.ok(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public Cookie createRefreshTokenCookie(final String refreshToken) {
cookie.setPath("/api/reissue");
cookie.setHttpOnly(true);
cookie.setSecure(true);

return cookie;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public enum ErrorCode {
NOT_FOUND_OAUTH(1010, "현재 지원하지 않는 OAuth 요청입니다."),
INVALID_REFRESH_TOKEN(1011, "존재하지 않는 refreshToken 입니다."),
TOKEN_PAIR_NOT_MATCHING_EXCEPTION(1012, "올바르지 않은 TokenPair 입니다."),
DUPLICATE_NICKNAME(1013, "중복되는 닉네임입니다."),

// 2000: 킬링파트 - 좋아요, 댓글

Expand Down Expand Up @@ -68,15 +67,17 @@ public enum ErrorCode {
VOTING_SONG_NOT_EXIST(4008, "존재하지 않는 투표 노래입니다."),
VOTE_FOR_OTHER_PART(4009, "해당 투표는 다른 파트에 대한 투표입니다."),
DUPLICATE_VOTE_EXIST(4010, "중복된 투표입니다."),
// 5000: 사용자

// 5000: 사용자
EMPTY_EMAIL(5001, "이메일은 비어있을 수 없습니다."),
TOO_LONG_EMAIL(5002, "이메일은 100자를 초과할 수 없습니다."),
INVALID_EMAIL_FORM(5003, "이메일 형식에 맞지 않습니다."),
EMPTY_NICKNAME(5004, "닉네임은 비어있을 수 없습니다."),
TOO_LONG_NICKNAME(5005, "닉네임은 100자를 초과할 수 없습니다."),
TOO_LONG_NICKNAME(5005, "닉네임은 20자를 초과할 수 없습니다."),
EXIST_MEMBER(5006, "이미 회원가입 된 멤버입니다."),
MEMBER_NOT_EXIST(5007, "존재하지 않는 멤버입니다."),
DUPLICATE_NICKNAME(5008, "중복되는 닉네임입니다."),
TOO_SHORT_NICKNAME(5009, "닉네임은 2자 이상이어야 합니다."),

REQUEST_BODY_VALIDATION_FAIL(10001, ""),
WRONG_REQUEST_URL(10002, "URL의 pathVariable 은 비어있을 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shook.shook.auth.application.TokenProvider;
import shook.shook.auth.application.dto.TokenPair;
import shook.shook.auth.exception.AuthorizationException;
import shook.shook.auth.repository.InMemoryTokenPairRepository;
import shook.shook.auth.ui.argumentresolver.MemberInfo;
Expand Down Expand Up @@ -62,7 +61,7 @@ public Member findByIdAndNicknameThrowIfNotExist(final Long id, final Nickname n

@Transactional
public void deleteById(final Long id, final MemberInfo memberInfo) {
final Member member = getMemberIfValidRequest(id, memberInfo);
final Member member = getMemberIfValidRequest(id, memberInfo.getMemberId());

final List<KillingPartLike> membersExistLikes = likeRepository.findAllByMemberAndIsDeleted(member, false);

Expand All @@ -71,8 +70,7 @@ public void deleteById(final Long id, final MemberInfo memberInfo) {
memberRepository.delete(member);
}

private Member getMemberIfValidRequest(final Long memberId, final MemberInfo memberInfo) {
final long requestMemberId = memberInfo.getMemberId();
private Member getMemberIfValidRequest(final Long memberId, final Long requestMemberId) {
final Member requestMember = findById(requestMemberId);
final Member targetMember = findById(memberId);
validateMemberAuthentication(requestMember, targetMember);
Expand All @@ -99,28 +97,20 @@ private Member findById(final Long id) {
}

@Transactional
public TokenPair updateNickname(final Long memberId, final MemberInfo memberInfo,
final NicknameUpdateRequest request) {
final Member member = getMemberIfValidRequest(memberId, memberInfo);
public boolean updateNickname(final Long memberId, final Long requestMemberId,
Copy link
Collaborator

Choose a reason for hiding this comment

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

💬 위의 deleteByIdmemberInfo 를 그대로 파라미터로 받아오고 있는데, 두 메서드가 파라미터를 받는 방식을 통일해보면 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

현재 memberInfo 의 id 만 쓰이고 있는 상태라 id 만 넘겨주는 걸로 통일했습니다 😄

Copy link
Collaborator

Choose a reason for hiding this comment

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

MemberService에서 Token을 재발급 해주는 로직이 사라지니 확실히 더 책임 분리가 잘 된 느낌이 드네용 ㅎㅎ 👍

final NicknameUpdateRequest request) {
final Member member = getMemberIfValidRequest(memberId, requestMemberId);
final Nickname nickname = new Nickname(request.getNickname());

if (member.hasSameNickname(nickname)) {
return null;
return false;
}

validateDuplicateNickname(nickname);
member.updateNickname(nickname.getValue());
memberRepository.save(member);

return reissueTokenPair(member.getId(), member.getNickname());
}

private TokenPair reissueTokenPair(final Long memberId, final String nickname) {
final String reissuedAccessToken = tokenProvider.createAccessToken(memberId, nickname);
final String reissuedRefreshToken = tokenProvider.createRefreshToken(memberId, nickname);
inMemoryTokenPairRepository.addOrUpdateTokenPair(reissuedRefreshToken, reissuedAccessToken);

return new TokenPair(reissuedAccessToken, reissuedRefreshToken);
return true;
}

private void validateDuplicateNickname(final Nickname nickname) {
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

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

이제 사용되지 않는 Response네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

매의 눈... 이거 왜 남아 있었을까요 🤦🏻‍♀️

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package shook.shook.member.application.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Schema(description = "닉네임 변경 응답")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class NicknameUpdateResponse {

@Schema(description = "액세스 토큰", example = "lfahrg;aoiehflksejflakwjeglk")
private String accessToken;

@Schema(description = "변경된 닉네임", example = "shook")
private String nickname;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
public class Nickname {

private static final int NICKNAME_MAXIMUM_LENGTH = 20;
private static final int NICKNAME_MINIMUM_LENGTH = 2;

@Column(name = "nickname", length = NICKNAME_MAXIMUM_LENGTH, nullable = false)
private String value;
Expand All @@ -35,5 +36,10 @@ private void validateNickname(final String value) {
Map.of("Nickname", value)
);
}
if (value.length() < NICKNAME_MINIMUM_LENGTH) {
throw new MemberException.TooShortNicknameException(
Map.of("Nickname", value)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,15 @@ public ExistNicknameException(final Map<String, String> inputValuesByProperty) {
super(ErrorCode.DUPLICATE_NICKNAME, inputValuesByProperty);
}
}

public static class TooShortNicknameException extends MemberException {

public TooShortNicknameException() {
super(ErrorCode.TOO_SHORT_NICKNAME);
}

public TooShortNicknameException(final Map<String, String> inputValuesByProperty) {
super(ErrorCode.TOO_SHORT_NICKNAME, inputValuesByProperty);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package shook.shook.member.ui;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -12,9 +10,6 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import shook.shook.auth.application.dto.ReissueAccessTokenResponse;
import shook.shook.auth.application.dto.TokenPair;
import shook.shook.auth.ui.CookieProvider;
import shook.shook.auth.ui.argumentresolver.Authenticated;
import shook.shook.auth.ui.argumentresolver.MemberInfo;
import shook.shook.member.application.MemberService;
Expand All @@ -27,7 +22,6 @@
public class MemberController implements MemberApi {

private final MemberService memberService;
private final CookieProvider cookieProvider;

@DeleteMapping
public ResponseEntity<Void> deleteMember(
Expand All @@ -40,23 +34,16 @@ public ResponseEntity<Void> deleteMember(
}

@PatchMapping("/nickname")
public ResponseEntity<ReissueAccessTokenResponse> updateNickname(
@PathVariable(name = "member_id") final Long memberId,
public ResponseEntity<Void> updateNickname(
@Authenticated final MemberInfo memberInfo,
@Valid @RequestBody final NicknameUpdateRequest request,
final HttpServletResponse response
@PathVariable(name = "member_id") final Long memberId,
@Valid @RequestBody final NicknameUpdateRequest request
) {
final TokenPair tokenPair = memberService.updateNickname(memberId, memberInfo, request);

if (tokenPair == null) {
return ResponseEntity.noContent().build();
final boolean isUpdated = memberService.updateNickname(memberId, memberInfo.getMemberId(), request);
if (isUpdated) {
return ResponseEntity.ok().build();
}

final Cookie cookie = cookieProvider.createRefreshTokenCookie(tokenPair.getRefreshToken());
response.addCookie(cookie);
final ReissueAccessTokenResponse reissueResponse = new ReissueAccessTokenResponse(
tokenPair.getAccessToken());

return ResponseEntity.ok(reissueResponse);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import shook.shook.auth.application.dto.ReissueAccessTokenResponse;
import shook.shook.auth.ui.argumentresolver.Authenticated;
import shook.shook.auth.ui.argumentresolver.MemberInfo;
import shook.shook.member.application.dto.NicknameUpdateRequest;
Expand Down Expand Up @@ -61,6 +60,10 @@ ResponseEntity<Void> deleteMember(
)
@Parameters(
value = {
@Parameter(
name = "memberInfo",
hidden = true
),
Comment on lines +63 to +66
Copy link
Collaborator

Choose a reason for hiding this comment

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

오홍 👍 👍

@Parameter(
name = "member_id",
description = "닉네임을 변경할 회원 id",
Expand All @@ -74,10 +77,9 @@ ResponseEntity<Void> deleteMember(
}
)
@PatchMapping("/nickname")
ResponseEntity<ReissueAccessTokenResponse> updateNickname(
@PathVariable(name = "member_id") final Long memberId,
ResponseEntity<Void> updateNickname(
@Authenticated final MemberInfo memberInfo,
@RequestBody final NicknameUpdateRequest request,
final HttpServletResponse response
@PathVariable(name = "member_id") final Long memberId,
@Valid @RequestBody final NicknameUpdateRequest request
);
}
Loading
Loading