Skip to content

Commit b2f4e1c

Browse files
authored
Merge pull request #54 from JocketDan/feat/user-edit
Feat/user edit 프로필 수정 api
2 parents 15701f4 + fd31820 commit b2f4e1c

7 files changed

Lines changed: 104 additions & 21 deletions

File tree

auth/src/main/java/com/jocketdan/auth/controller/UserController.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ public ResponseEntity<CommonResponse<UserResponse>> getMyProfile(
5555
return ResponseEntity.ok(CommonResponse.success(userFacade.getProfile(userDetails)));
5656
}
5757

58+
@PatchMapping("/edit")
59+
public ResponseEntity<CommonResponse<Void>> patchNickName(
60+
@AuthenticationPrincipal UserDetails userDetails,
61+
@RequestBody @Valid EditProfileRequest request) {
62+
userFacade.changeNickName(userDetails, request);
63+
return ResponseEntity.ok(CommonResponse.success("프로필 변경이 완료되었습니다.", null));
64+
}
65+
5866
@DeleteMapping("")
5967
public ResponseEntity<CommonResponse<Void>> withdraw(
6068
@AuthenticationPrincipal UserDetails userDetails,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.jocketdan.auth.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
import lombok.extern.jackson.Jacksonized;
6+
7+
@Getter
8+
@Builder
9+
@Jacksonized
10+
public class EditProfileRequest {
11+
private String newNickName;
12+
private String emoji;
13+
}

auth/src/main/java/com/jocketdan/auth/dto/UserResponse.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import com.jocketdan.auth.entity.User;
44

5-
public record UserResponse(String email, String nickname, String provider) {
5+
public record UserResponse(String email, String nickname, String provider, String emoji) {
66
public static UserResponse of(User user) {
7-
return new UserResponse(user.getEmail(), user.getNickname(), String.valueOf(user.getProvider()));
7+
return new UserResponse(user.getEmail(), user.getNickname(), String.valueOf(user.getProvider()), user.getEmoji());
88
}
99
}

auth/src/main/java/com/jocketdan/auth/entity/User.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import jakarta.persistence.GenerationType;
1111
import jakarta.persistence.Id;
1212
import jakarta.persistence.Table;
13-
import lombok.AccessLevel;
1413
import lombok.AllArgsConstructor;
1514
import lombok.Getter;
1615
import lombok.NoArgsConstructor;
@@ -31,7 +30,7 @@ public class User extends BaseTimeEntity {
3130

3231
private String password; // 자체 회원가입한 유저만 저장 -> 소셜 로그인의 경우 토큰으로 처리
3332

34-
@Column(nullable = false)
33+
@Column(unique = true, nullable = false, length = 20)
3534
private String nickname;
3635

3736
@Column(nullable = false)
@@ -52,24 +51,31 @@ public class User extends BaseTimeEntity {
5251
@Column(nullable = false)
5352
private boolean credentialsNonExpired = true;
5453

54+
@Column(length = 10)
55+
private String emoji;
56+
5557
public static User signup(String email, String password, String nickname, String role, AuthProvider provider) {
5658
User user = new User();
5759
user.email = email;
5860
user.password = password;
5961
user.nickname = nickname;
6062
user.role = role;
6163
user.provider = provider;
64+
user.emoji = "👤";
6265
return user;
6366
}
6467
public void changeIdForTest(Long id){
6568
this.id = id;
6669
}
70+
public void changePassword(String password){
71+
this.password = password;
72+
}
6773

6874
public void changeNickname(String name){
6975
this.nickname = name;
7076
}
71-
public void changePassword(String password){
72-
this.password = password;
73-
}
7477

78+
public void changeEmoji(String emoji) {
79+
this.emoji = emoji.trim();
80+
}
7581
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.jocketdan.auth.exception;
2+
3+
public class DuplicateNicknameException extends RuntimeException {
4+
5+
public DuplicateNicknameException(String message) {
6+
super(message);
7+
}
8+
}

auth/src/main/java/com/jocketdan/auth/service/user/UserFacade.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ public UserResponse getProfile(UserDetails userDetails) {
3939
return userService.getProfileById(userDetails);
4040
}
4141

42+
43+
// 유저 정보 변경
44+
public void changeNickName(UserDetails userDetails, EditProfileRequest request){
45+
userService.changeProfile(userDetails, request);
46+
}
47+
4248
// 회원 탈퇴
4349
public void withdraw(UserDetails userDetails, HttpServletRequest req, HttpServletResponse res) {
4450
String refreshToken = tokenCookieService.getRefreshToken(req);
Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package com.jocketdan.auth.service.user;
22

3+
import com.jocketdan.auth.dto.EditProfileRequest;
34
import com.jocketdan.auth.dto.UserResponse;
45
import com.jocketdan.auth.entity.User;
6+
import com.jocketdan.auth.exception.DuplicateNicknameException;
57
import com.jocketdan.auth.exception.UserNotFoundException;
68
import com.jocketdan.auth.repository.UserRepository;
79
import com.jocketdan.auth.service.auth.TokenService;
810
import lombok.RequiredArgsConstructor;
911
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.dao.DataIntegrityViolationException;
1013
import org.springframework.security.core.userdetails.UserDetails;
1114
import org.springframework.stereotype.Service;
1215
import org.springframework.transaction.annotation.Transactional;
1316

1417
/**
15-
* 기존의 복잡한 기능은 각 서비스로 분리 후,
16-
* 정말 유저 관리만 초점
17-
* */
18+
* 기존의 복잡한 기능은 각 서비스로 분리 후, 정말 유저 관리만 초점
19+
*/
1820
@Slf4j
1921
@Service
2022
@RequiredArgsConstructor
@@ -24,28 +26,68 @@ public class UserService {
2426
private final TokenService tokenService;
2527

2628
public UserResponse getProfileById(UserDetails userDetails) {
27-
User user = userRepository.findByEmail(userDetails.getUsername())
28-
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
29+
User user = findUser(userDetails.getUsername());
2930
return UserResponse.of(user);
3031
}
3132

33+
// @Transactional(isolation = Isolation.READ_COMMITTED)
3234
@Transactional
33-
public void changeNickname(Long userId, String newNickName) {
34-
User user = userRepository.findById(userId)
35-
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
36-
if (userRepository.existsByNickname(newNickName)) {
37-
throw new IllegalArgumentException("이미 사용 중인 닉네임입니다.");
35+
public void changeProfile(UserDetails userDetails, EditProfileRequest request) {
36+
User user = findUser(userDetails.getUsername());
37+
try {
38+
if (request.getNewNickName() != null
39+
&& !request.getNewNickName().isBlank()
40+
&& !user.getNickname().equals(request.getNewNickName().trim())
41+
) {
42+
validateNickname(request.getNewNickName()); // 검증
43+
user.changeNickname(request.getNewNickName()); // 이후 저장
44+
}
45+
46+
if (request.getEmoji() != null && !request.getEmoji()
47+
.isBlank()) {
48+
validateEmoji(request.getEmoji());
49+
user.changeEmoji(request.getEmoji());
50+
}
51+
log.info("회원 프로필 변경 완료: email={}", user.getEmail());
52+
} catch (DataIntegrityViolationException e) {
53+
log.warn("닉네임 중복 시도: email={}, nickname={}",
54+
user.getEmail(), request.getNewNickName());
55+
throw new DuplicateNicknameException("이미 사용 중인 닉네임입니다.");
56+
} catch (IllegalArgumentException e){
57+
throw new IllegalArgumentException("형식에 맞지 않습니다.");
3858
}
39-
user.changeNickname(newNickName);
40-
userRepository.save(user);
4159
}
4260

4361
@Transactional
4462
public void withdrawUser(String email) {
45-
User user = userRepository.findByEmail(email)
46-
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
63+
User user = findUser(email);
4764
tokenService.revokeAllRefreshTokensByUserId(user.getId());
4865
userRepository.delete(user);
4966
log.info("회원 탈퇴 완료: email={}", email);
5067
}
68+
69+
private User findUser(String email) {
70+
return userRepository.findByEmail(email)
71+
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
72+
}
73+
74+
private void validateEmoji(String emoji) {
75+
if (emoji.length() > 10) {
76+
throw new IllegalArgumentException("이모지는 10자 이하여야 합니다.");
77+
}
78+
}
79+
80+
private void validateNickname(String nickname) {
81+
if (nickname == null || nickname.isBlank()) {
82+
throw new IllegalArgumentException("닉네임은 필수입니다.");
83+
}
84+
String trimmed = nickname.trim();
85+
if (trimmed.length() < 2 || trimmed.length() > 20) {
86+
throw new IllegalArgumentException("닉네임은 2~20자여야 합니다.");
87+
}
88+
// 특수문자 검증
89+
if (!trimmed.matches("^[a-zA-Z0-9가-힣_]+$")) {
90+
throw new IllegalArgumentException("닉네임은 영문, 한글, 숫자, _만 가능합니다.");
91+
}
92+
}
5193
}

0 commit comments

Comments
 (0)