From fdf5a3dbb38ca61fe3c0330cef55b0c71c70f67d Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Tue, 12 Aug 2025 17:58:29 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[feat]=20#71=20=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DGU_AI_LAB/admin_be/domain/users/entity/User.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/users/entity/User.java b/src/main/java/DGU_AI_LAB/admin_be/domain/users/entity/User.java index 4c2945b7..3b634ac7 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/users/entity/User.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/users/entity/User.java @@ -48,4 +48,12 @@ public void updateUserInfo(String encodedPassword, Boolean isActive) { if (encodedPassword != null) this.password = encodedPassword; if (isActive != null) this.isActive = isActive; } + + public void updatePassword(String newEncodedPassword) { + this.password = newEncodedPassword; + } + + public void updatePhone(String newPhone) { + this.phone = newPhone; + } } From 2404ef58ffa06fff05f3a5dd0cd578b00e0bbc14 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Tue, 12 Aug 2025 17:59:49 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[feat]=20#71=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8/=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../users/dto/request/PasswordUpdateRequestDTO.java | 12 ++++++++++++ .../users/dto/request/PhoneUpdateRequestDTO.java | 10 ++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PasswordUpdateRequestDTO.java create mode 100644 src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PhoneUpdateRequestDTO.java diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PasswordUpdateRequestDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PasswordUpdateRequestDTO.java new file mode 100644 index 00000000..e1da27b4 --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PasswordUpdateRequestDTO.java @@ -0,0 +1,12 @@ +package DGU_AI_LAB.admin_be.domain.users.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record PasswordUpdateRequestDTO( + @NotBlank(message = "현재 비밀번호는 필수로 입력해야 합니다.") + String currentPassword, + @NotBlank(message = "새 비밀번호는 필수로 입력해야 합니다.") + // @Size(min = 8, message = "새 비밀번호는 최소 8자 이상이어야 합니다.") + String newPassword +) {} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PhoneUpdateRequestDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PhoneUpdateRequestDTO.java new file mode 100644 index 00000000..a40df7fa --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/users/dto/request/PhoneUpdateRequestDTO.java @@ -0,0 +1,10 @@ +package DGU_AI_LAB.admin_be.domain.users.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +public record PhoneUpdateRequestDTO( + @NotBlank(message = "연락처는 필수로 입력해야 합니다.") + @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "유효한 전화번호 형식이 아닙니다. (예: 010-1234-5678)") + String newPhone +) {} \ No newline at end of file From 14b2854126b300ba0c7edc39dbfd2c20b7c9fb8d Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Tue, 12 Aug 2025 17:59:59 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[feat]=20#71=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8/=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/users/service/UserService.java | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java index 1ebc1850..178c70ec 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java @@ -2,6 +2,8 @@ import DGU_AI_LAB.admin_be.domain.groups.entity.Group; import DGU_AI_LAB.admin_be.domain.groups.repository.GroupRepository; +import DGU_AI_LAB.admin_be.domain.users.dto.request.PasswordUpdateRequestDTO; +import DGU_AI_LAB.admin_be.domain.users.dto.request.PhoneUpdateRequestDTO; import DGU_AI_LAB.admin_be.domain.users.dto.request.UserCreateRequestDTO; import DGU_AI_LAB.admin_be.domain.users.dto.request.UserUpdateRequestDTO; import DGU_AI_LAB.admin_be.domain.users.dto.response.MyInfoResponseDTO; @@ -10,9 +12,11 @@ import DGU_AI_LAB.admin_be.domain.users.entity.User; import DGU_AI_LAB.admin_be.domain.users.repository.UserRepository; import DGU_AI_LAB.admin_be.error.ErrorCode; +import DGU_AI_LAB.admin_be.error.exception.BusinessException; import DGU_AI_LAB.admin_be.error.exception.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,7 +29,7 @@ public class UserService { private final UserRepository userRepository; - private final GroupRepository groupRepository; + private final PasswordEncoder passwordEncoder; private static final long UID_BASE = 10000; // TODO: 이부분 시스템에 맞추어서 수정하기 @@ -86,4 +90,45 @@ public MyInfoResponseDTO getMyInfo(Long userId) { .orElseThrow(() -> new EntityNotFoundException(ErrorCode.ENTITY_NOT_FOUND)); return MyInfoResponseDTO.fromEntity(user); } -} + + /** + * 사용자 비밀번호 변경 + */ + public UserResponseDTO updatePassword(Long userId, PasswordUpdateRequestDTO request) { + log.info("[updatePassword] userId={} 비밀번호 변경 시도", userId); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.USER_NOT_FOUND)); // ⭐ USER_NOT_FOUND 사용 + + // 현재 비밀번호 확인 + if (!passwordEncoder.matches(request.currentPassword(), user.getPassword())) { + log.warn("[updatePassword] userId={} 현재 비밀번호 불일치", userId); + throw new BusinessException(ErrorCode.INVALID_PASSWORD); + } + + // 새 비밀번호가 현재 비밀번호와 동일한지 확인 + if (passwordEncoder.matches(request.newPassword(), user.getPassword())) { + log.warn("[updatePassword] userId={} 새 비밀번호가 현재 비밀번호와 동일", userId); + throw new BusinessException(ErrorCode.PASSWORD_CHANGE_SAME_AS_OLD); + } + + // 새 비밀번호 암호화 및 업데이트 + String encodedNewPassword = passwordEncoder.encode(request.newPassword()); + user.updatePassword(encodedNewPassword); + log.info("[updatePassword] userId={} 비밀번호 변경 완료", userId); + return UserResponseDTO.fromEntity(user); + } + + /** + * 사용자 연락처 변경 + */ + public UserResponseDTO updatePhone(Long userId, PhoneUpdateRequestDTO request) { + log.info("[updatePhone] userId={} 연락처 변경 시도", userId); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.USER_NOT_FOUND)); + user.updatePhone(request.newPhone()); + log.info("[updatePhone] userId={} 연락처 변경 완료", userId); + return UserResponseDTO.fromEntity(user); + } +} \ No newline at end of file From 9764cf19586e771f88f9bd23f7fe0e90d3f393b1 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Tue, 12 Aug 2025 18:00:04 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[feat]=20#71=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8/=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../users/controller/UserController.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/UserController.java b/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/UserController.java index 1e8c0845..227590e3 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/UserController.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/users/controller/UserController.java @@ -1,14 +1,16 @@ package DGU_AI_LAB.admin_be.domain.users.controller; +import DGU_AI_LAB.admin_be.domain.users.dto.request.PasswordUpdateRequestDTO; +import DGU_AI_LAB.admin_be.domain.users.dto.request.PhoneUpdateRequestDTO; +import DGU_AI_LAB.admin_be.domain.users.dto.response.UserResponseDTO; import DGU_AI_LAB.admin_be.domain.users.service.UserService; import DGU_AI_LAB.admin_be.global.auth.CustomUserDetails; import DGU_AI_LAB.admin_be.global.common.SuccessResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -17,11 +19,39 @@ public class UserController { private final UserService userService; + /** + * 사용자 정보 확인 API + * @param principal + * @return + */ @GetMapping("/me") public ResponseEntity> getMyInfo( @AuthenticationPrincipal CustomUserDetails principal ) { return SuccessResponse.ok(userService.getMyInfo(principal.getUserId())); } -} + /** + * 사용자 비밀번호 변경 API + * PATCH /api/users/me/password + */ + @PatchMapping("/me/password") + public ResponseEntity> updateUserPassword(@AuthenticationPrincipal CustomUserDetails principal, + @RequestBody @Valid PasswordUpdateRequestDTO request + ) { + UserResponseDTO updatedUser = userService.updatePassword(principal.getUserId(), request); + return SuccessResponse.ok(updatedUser); + } + + /** + * 사용자 연락처 변경 API + * PATCH /api/users/me/phone + */ + @PatchMapping("/me/phone") + public ResponseEntity> updateUserPhone(@AuthenticationPrincipal CustomUserDetails principal, + @RequestBody @Valid PhoneUpdateRequestDTO request + ) { + UserResponseDTO updatedUser = userService.updatePhone(principal.getUserId(), request); + return SuccessResponse.ok(updatedUser); + } +} \ No newline at end of file From a7f514767ac67973486c4d4978ae4dd33c43c873 Mon Sep 17 00:00:00 2001 From: saokiritoni Date: Tue, 12 Aug 2025 18:00:10 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[feat]=20#71=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java index 435d55a9..00bb5a03 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java +++ b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java @@ -75,6 +75,10 @@ public enum ErrorCode { INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "올바르지 않은 인증 코드입니다."), GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "지정된 그룹을 찾을 수 없습니다."), UID_ALLOCATION_FAILED(HttpStatus.BAD_REQUEST, "UID를 할당하는 데 실패했습니다."), // 이 부분 고민 + INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "현재 비밀번호가 일치하지 않습니다."), + PASSWORD_CHANGE_SAME_AS_OLD(HttpStatus.BAD_REQUEST, "새 비밀번호가 현재 비밀번호와 동일합니다."), + + /** * Approval Error