From 8f19b12cff568cc8725076b891669c7b27fd0114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Tue, 31 Dec 2024 17:39:02 +0900 Subject: [PATCH 01/13] =?UTF-8?q?Refactor=20:=20UserServiceImpl=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/component/AuthComponent.java | 192 ++++++++++++++++++ .../domain/user/component/EmailComponent.java | 59 ++++++ .../domain/user/component/HeartComponent.java | 104 ++++++++++ .../domain/user/component/KaKaoComponent.java | 51 +++++ .../user/component/LocationComponent.java | 55 +++++ .../domain/user/component/PageComponent.java | 112 ++++++++++ .../user/component/PasswordComponent.java | 82 ++++++++ .../user/component/RecommendComponent.java | 68 +++++++ .../domain/user/component/UserComponent.java | 87 ++++++++ 9 files changed, 810 insertions(+) create mode 100644 src/main/java/com/petmatz/domain/user/component/AuthComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/EmailComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/HeartComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/LocationComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/PageComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/PasswordComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/RecommendComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/UserComponent.java diff --git a/src/main/java/com/petmatz/domain/user/component/AuthComponent.java b/src/main/java/com/petmatz/domain/user/component/AuthComponent.java new file mode 100644 index 0000000..f03ed58 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/AuthComponent.java @@ -0,0 +1,192 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.domain.aws.AwsClient; +import com.petmatz.domain.aws.vo.S3Imge; +import com.petmatz.domain.user.entity.Certification; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.entity.UserFactory; +import com.petmatz.domain.user.info.CheckCertificationInfo; +import com.petmatz.domain.user.info.SignInInfo; +import com.petmatz.domain.user.info.SignUpInfo; +import com.petmatz.domain.user.repository.CertificationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.CheckCertificationResponseDto; +import com.petmatz.domain.user.response.SignInResponseDto; +import com.petmatz.domain.user.response.SignUpResponseDto; +import com.petmatz.domain.user.service.GeocodingService; +import com.petmatz.user.common.LogInResponseDto; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Component +@RequiredArgsConstructor +@Slf4j +public class AuthComponent { + + /** + * awsClient -> 추후에 infra 로 옮길 예정 + * 현재 1차로 분리작업만 하는중 + */ + + private final UserRepository userRepository; + private final CertificationRepository certificationRepository; + private final GeocodingService geocodingService; + private final AwsClient awsClient; // 추후에 수정 + private final JwtProvider jwtProvider; + private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Transactional + public ResponseEntity signUp(SignUpInfo info) { + try { + String accountId = info.getAccountId(); + String certificationNumber = info.getCertificationNumber(); + + // 1. 필수 정보 누락 확인 + if (accountId == null || certificationNumber == null || info.getPassword() == null) { + return SignUpResponseDto.missingRequiredFields(); + } + + // 2. 인증 번호 확인 + Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); + if (certification == null || !certification.getIsVerified()) { + return SignUpResponseDto.certificationFail(); // 인증되지 않은 경우 + } + + // 3. 중복된 ID 확인 + if (userRepository.existsByAccountId(accountId)) { + return SignUpResponseDto.duplicateId(); + } + + // 5. 비밀번호 암호화 후 저장 + String encodedPassword = passwordEncoder.encode(info.getPassword()); + + // 6. GeocodingService를 통해 지역명과 6자리 행정코드 가져오기 + GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); + if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { + return SignUpResponseDto.locationFail(); + } + + //6-1 Img 정제 + S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); + + // 7. 새로운 User 생성 및 저장 + User user = UserFactory.createNewUser(info, encodedPassword, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger(), petImg.uploadURL()); + userRepository.save(user); + + // 8. 인증 엔티티 삭제 + certificationRepository.deleteAllByAccountId(accountId); + + // 9. 성공 응답 반환 + return SignUpResponseDto.success(user.getId(), petImg.checkResultImg()); + + } catch (RuntimeException e) { + log.error("회원 가입 실패: {}", e.getMessage(), e); + throw e; + } catch (Exception e) { + log.error("회원 가입 중 처리되지 않은 예외 발생: {}", e.getMessage(), e); + return SignUpResponseDto.unknownError(); + } + } + + + public ResponseEntity signIn(SignInInfo info, HttpServletResponse response) { + try { + String accountId = info.getAccountId(); + User user = userRepository.findByAccountId(accountId); + // 사용자 존재 여부 확인 + if (user == null) { + log.info("사용자 조회 실패: {}", accountId); + return SignInResponseDto.signInFail(); + } + + // 비밀번호 확인 + String password = info.getPassword(); + String encodedPassword = user.getPassword(); + if (!passwordEncoder.matches(password, encodedPassword)) { + log.info("비밀번호 불일치: {}", accountId); + return SignInResponseDto.signInFail(); + } + + // JWT 생성 (userId를 subject로, accountId를 클레임으로 설정) + String token = jwtProvider.create(user.getId(), user.getAccountId()); + log.info("JWT 생성 완료: {}", token); + + ResponseCookie responseCookie = ResponseCookie.from("jwt", token) + .httpOnly(true) // XSS 방지 + .secure(true) // HTTPS만 허용 + .path("/") // 모든 경로에서 접근 가능 + .sameSite("None") // SameSite=None 설정 + .maxAge((3600)) + .build(); + response.addHeader("Set-Cookie", responseCookie.toString()); + // 로그인 성공 응답 반환 + return SignInResponseDto.success(user); // User 객체 전달 + } catch (Exception e) { + log.error("로그인 처리 중 예외 발생", e); + return SignInResponseDto.signInFail(); + } + } + + public ResponseEntity logout(HttpServletResponse response) { + try { + // 만료된 쿠키 설정 + ResponseCookie expiredCookie = ResponseCookie.from("jwt", "") + .httpOnly(true) // XSS 방지 + .secure(true) // HTTPS만 허용 + .path("/") // 모든 경로에서 접근 가능 + .sameSite("None") // SameSite=None 설정 + .maxAge(0) // 즉시 만료 + .build(); + + response.addHeader("Set-Cookie", expiredCookie.toString()); + + log.info("JWT 쿠키 제거 및 로그아웃 처리 완료"); + return LogInResponseDto.success(); + } catch (Exception e) { + log.error("로그아웃 처리 중 예외 발생", e); + return LogInResponseDto.validationFail(); + } + } + + @Transactional + public ResponseEntity checkCertification(CheckCertificationInfo info) { + try { + String accountId = info.getAccountId(); + String certificationNumber = info.getCertificationNumber(); + + Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); + if (certification == null) return CheckCertificationResponseDto.certificationFail(); + + boolean isMatch = certification.getAccountId().equals(accountId) + && certification.getCertificationNumber().equals(certificationNumber); + + if (!isMatch) return CheckCertificationResponseDto.certificationFail(); + + // 인증 시간 검증 + LocalDateTime createdAt = certification.getCreatedAt(); + if (createdAt.isBefore(LocalDateTime.now().minusMinutes(5))) { // 5분 유효시간 + return CheckCertificationResponseDto.certificationExpired(); + } + + // 인증 완료 상태 업데이트 + certification.markAsVerified(); + certificationRepository.save(certification); + + } catch (Exception e) { + log.info("인증 번호 확인 실패: {}", e); + return CheckCertificationResponseDto.databaseError(); + } + + return CheckCertificationResponseDto.success(); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/EmailComponent.java b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java new file mode 100644 index 0000000..9de0423 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java @@ -0,0 +1,59 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.api.user.request.EmailCertificationRequestDto; +import com.petmatz.domain.user.entity.Certification; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.provider.CertificationNumberProvider; +import com.petmatz.domain.user.repository.CertificationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.EmailCertificationResponseDto; +import com.petmatz.domain.user.response.GetMyUserDto; +import com.petmatz.domain.user.service.EmailProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class EmailComponent { + + private final UserRepository userRepository; + private final EmailProvider emailProvider; + private final CertificationRepository certificationRepository; + + public ResponseEntity emailCertification(EmailCertificationRequestDto dto) { + try { + String accountId = dto.getAccountId(); + + //이메일 전송과 동시에 아이디 중복검사 + boolean isExistId = userRepository.existsByAccountId(accountId); + if (isExistId) return EmailCertificationResponseDto.duplicateId(); + + // 인증 번호 생성 및 이메일 전송 + String certificationNumber = CertificationNumberProvider.generateNumber(); + boolean isSendSuccess = emailProvider.sendVerificationEmail(accountId, certificationNumber); + if (!isSendSuccess) return EmailCertificationResponseDto.mailSendFail(); + + // 인증 엔티티 저장 + Certification certification = Certification.builder().accountId(accountId).certificationNumber(certificationNumber).isVerified(false).build(); + certificationRepository.save(certification); + + } catch (Exception e) { + log.info("이메일 인증 실패: {}", e); + return EmailCertificationResponseDto.databaseError(); + } + return EmailCertificationResponseDto.success(); + } + + public GetMyUserDto receiverEmail(String accountId) { + try { + User user = userRepository.findByAccountId(accountId); + return new GetMyUserDto(user); + } catch (Exception e) { + e.printStackTrace(); + } + return new GetMyUserDto(); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/HeartComponent.java b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java new file mode 100644 index 0000000..8b1c57d --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java @@ -0,0 +1,104 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.api.user.request.HeartedUserDto; +import com.petmatz.api.user.request.HeartingRequestDto; +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.entity.Heart; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.entity.UserFactory; +import com.petmatz.domain.user.repository.HeartRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.GetHeartingListResponseDto; +import com.petmatz.domain.user.response.HeartingResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class HeartComponent { + + private final UserRepository userRepository; + private final JwtExtractProvider jwtExtractProvider; + private final HeartRepository heartRepository; + + + @Transactional + public ResponseEntity hearting(HeartingRequestDto dto) { + try { + Long heartedId = dto.getHeartedId(); + + // 대상 사용자가 존재하는지 확인 + boolean exists = userRepository.existsById(heartedId); + if (!exists) { + return HeartingResponseDto.heartedIdNotFound(); + } + + // 현재 사용자 ID 가져오기 + Long userId = jwtExtractProvider.findIdFromJwt(); + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + // DB에서 myId와 heartedId로 Heart 레코드 확인 + Optional existingHeart = heartRepository.findByMyIdAndHeartedId(user.getId(), heartedId); + + if (existingHeart.isPresent()) { + // 찜하기 해제 (DB에서 삭제) + heartRepository.delete(existingHeart.get()); + return HeartingResponseDto.success(); // 찜하기 해제 성공 응답 + } + + // 찜하기 진행 (DB에 저장) + Heart heart = UserFactory.createHeart(userId, heartedId); + heartRepository.save(heart); + + return HeartingResponseDto.success(); // 찜하기 성공 응답 + + } catch (Exception e) { + log.info("찜하기 실패: {}", e); + return HeartingResponseDto.databaseError(); // 데이터베이스 오류 응답 + } + } + + + public ResponseEntity getHeartedList() { + try { + Long userId = jwtExtractProvider.findIdFromJwt(); + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + // Heart 리스트 조회 + List heartList = heartRepository.findAllByMyId(user.getId()); + + // Heart 정보와 관련된 User 데이터 매핑 + List heartedUsers = heartList.stream() + .map(heart -> { + User heartedUser = userRepository.findById(heart.getHeartedId()) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + heart.getHeartedId())); + + return new HeartedUserDto( + heart.getMyId(), + heart.getHeartedId(), + heartedUser.getNickname(), + heartedUser.getProfileImg(), + heartedUser.getCareAvailable(), + heartedUser.getPreferredSizes() + ); + }) + .toList(); + + // 성공 응답 반환 + return GetHeartingListResponseDto.success(heartedUsers); + + } catch (Exception e) { + log.info("찜리스트 받아오기 실패: {}", e); + return HeartingResponseDto.databaseError(); + } + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java b/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java new file mode 100644 index 0000000..d006199 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java @@ -0,0 +1,51 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.EditKakaoProfileInfo; +import com.petmatz.domain.user.response.EditKakaoProfileResponseDto; +import com.petmatz.domain.user.response.UpdateLocationResponseDto; +import com.petmatz.domain.user.service.GeocodingService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class KaKaoComponent { + + @Transactional + public ResponseEntity editKakaoProfile(EditKakaoProfileInfo info) { + try { + Long userId = jwtExtractProvider.findIdFromJwt(); + + // userId가 null인 경우 예외 처리 + if (userId == null) { + log.warn("JWT에서 추출된 userId가 null입니다."); + return EditKakaoProfileResponseDto.idNotFound(); + } + + boolean exists = userRepository.existsById(userId); + if (!exists) { + log.warn("존재하지 않는 사용자 ID: {}", userId); + return EditKakaoProfileResponseDto.idNotFound(); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); + if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { + return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 + } + + user.updateKakaoProfile(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); + } catch (Exception e) { + log.error("프로필 수정 실패", e); + return EditKakaoProfileResponseDto.editFailed(); + } + return EditKakaoProfileResponseDto.success(); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/LocationComponent.java b/src/main/java/com/petmatz/domain/user/component/LocationComponent.java new file mode 100644 index 0000000..d7ac3ff --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/LocationComponent.java @@ -0,0 +1,55 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.UpdateLocationInfo; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.UpdateLocationResponseDto; +import com.petmatz.domain.user.service.GeocodingService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class LocationComponent { + + private final JwtExtractProvider jwtExtractProvider; + private final UserRepository userRepository; + private final GeocodingService geocodingService; + + @Transactional + public ResponseEntity updateLocation(UpdateLocationInfo info) { + try { + // JWT에서 사용자 ID 추출 + Long userId = jwtExtractProvider.findIdFromJwt(); + + // 사용자 엔티티 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + // 사용자 존재 여부 확인 + boolean exists = userRepository.existsById(userId); + if (!exists) { + return UpdateLocationResponseDto.userNotFound(); + } + + // GeocodingService에서 지역명과 행정코드 가져오기 + GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); + if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { + return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 + } + + user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); + + // 성공 응답 반환 + return UpdateLocationResponseDto.success(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); + } catch (Exception e) { + log.error("위치 업데이트 실패: {}", e.getMessage(), e); + return UpdateLocationResponseDto.wrongLocation(); + } + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/PageComponent.java b/src/main/java/com/petmatz/domain/user/component/PageComponent.java new file mode 100644 index 0000000..110d0c9 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/PageComponent.java @@ -0,0 +1,112 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.aws.AwsClient; +import com.petmatz.domain.aws.vo.S3Imge; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.EditMyProfileInfo; +import com.petmatz.domain.user.repository.HeartRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.EditMyProfileResponseDto; +import com.petmatz.domain.user.response.GetMyProfileResponseDto; +import com.petmatz.domain.user.response.GetOtherProfileResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PageComponent { + + /** + * 공통 부분 좀 빼야 할 것 같음 + */ + + private final JwtExtractProvider jwtExtractProvider; + private final UserRepository userRepository; + private final HeartRepository heartRepository; + private final AwsClient awsClient; + + + public ResponseEntity getMypage() { + try { + String userId = jwtExtractProvider.findAccountIdFromJwt(); + User user = userRepository.findByAccountId(userId); + + boolean exists = userRepository.existsByAccountId(userId); + if (!exists) { + return GetMyProfileResponseDto.idNotFound(); + } + + return GetMyProfileResponseDto.success(user); + + } catch (Exception e) { + e.printStackTrace(); + return GetMyProfileResponseDto.databaseError(); + } + } + + public ResponseEntity getOtherMypage(Long userId) { + try { + // 현재 로그인한 사용자 ID 가져오기 + Long myId = jwtExtractProvider.findIdFromJwt(); + if (!userRepository.existsById(myId)) { + return GetMyProfileResponseDto.idNotFound(); + } + + // 조회 대상 사용자 정보 가져오기 + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + // 조회 대상 사용자가 존재하는지 확인 + boolean exists = userRepository.existsById(userId); + if (!exists) { + return GetOtherProfileResponseDto.userNotFound(); + } + + // 현재 로그인한 사용자가 조회 대상 사용자를 찜했는지 확인 + boolean isMyHeartUser = heartRepository.existsByMyIdAndHeartedId(myId, userId); + + // 응답 생성 + return GetOtherProfileResponseDto.success(user, isMyHeartUser); + + } catch (Exception e) { + e.printStackTrace(); + return GetOtherProfileResponseDto.userNotFound(); + } + } + + //TODO 고쳐야함. ...? + @Transactional + public ResponseEntity editMyProfile(EditMyProfileInfo info) { + try { + System.out.println("info ::" + info.isCareAvailable()); + Long userId = jwtExtractProvider.findIdFromJwt(); + String userEmail = jwtExtractProvider.findAccountIdFromJwt(); + boolean exists = userRepository.existsById(userId); + if (!exists) { + return EditMyProfileResponseDto.idNotFound(); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + //6-1 Img 정제 + S3Imge petImg = awsClient.UploadImg(userEmail, info.getProfileImg(), "CUSTOM_USER_IMG", null); + + // 병합된 DTO를 기반으로 엔티티 생성 + String resultImg = user.updateImgURL(info.getProfileImg(), petImg); + user.updateProfile(info); + +// 반환해야 함 아래꺼 + return EditMyProfileResponseDto.success(resultImg); + + } catch (Exception e) { + log.info("프로필 수정 실패: {}", e); + return EditMyProfileResponseDto.editFailed(); + } + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java b/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java new file mode 100644 index 0000000..f3e03e2 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java @@ -0,0 +1,82 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.api.user.request.SendRepasswordRequestDto; +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.RepasswordInfo; +import com.petmatz.domain.user.provider.RePasswordProvider; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.GetMyProfileResponseDto; +import com.petmatz.domain.user.response.RepasswordResponseDto; +import com.petmatz.domain.user.response.SendRepasswordResponseDto; +import com.petmatz.domain.user.service.RePasswordEmailProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PasswordComponent { + + private final UserRepository userRepository; + private final RePasswordEmailProvider rePasswordEmailProvider; + private final JwtExtractProvider jwtExtractProvider; + private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Transactional + public ResponseEntity sendRepassword(SendRepasswordRequestDto dto) { + try { + String accountId = dto.getAccountId(); + if (!userRepository.existsByAccountId(accountId)) { + return GetMyProfileResponseDto.idNotFound(); + } + User user = userRepository.findByAccountId(accountId); + + String rePasswordNum = RePasswordProvider.generatePassword(); + log.info("Generated Repassword: {}", rePasswordNum); + + boolean isSendSuccess = rePasswordEmailProvider.sendVerificationEmail(accountId, rePasswordNum); + if (!isSendSuccess) return SendRepasswordResponseDto.mailSendFail(); + + String encodedRePasswordNum = passwordEncoder.encode(rePasswordNum); + + user.updatePassword(encodedRePasswordNum); + + } catch (Exception e) { + log.info("임시비밀번호 재설정 실패: {}", e); + return SendRepasswordResponseDto.databaseError(); + } + return SendRepasswordResponseDto.success(); + } + + @Transactional + public ResponseEntity repassword(RepasswordInfo info) { + try { + Long userId = jwtExtractProvider.findIdFromJwt(); + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + String currentPassword = info.getCurrentPassword(); + + boolean isPasswordValid = passwordEncoder.matches(currentPassword, user.getPassword()); + if (!isPasswordValid) { + return RepasswordResponseDto.wrongPassword(); + } + String newPassword = info.getNewPassword(); + + String encodedNewPassword = passwordEncoder.encode(newPassword); + + user.updatePassword(encodedNewPassword); + + } catch (Exception e) { + log.info("비밀번호 재설정 실패: {}", e); + return RepasswordResponseDto.databaseError(); + } + return RepasswordResponseDto.success(); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/RecommendComponent.java b/src/main/java/com/petmatz/domain/user/component/RecommendComponent.java new file mode 100644 index 0000000..5ad3f25 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/RecommendComponent.java @@ -0,0 +1,68 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.api.user.request.UpdateRecommendationRequestDto; +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.entity.Recommendation; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.entity.UserFactory; +import com.petmatz.domain.user.repository.RecommendationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.GetRecommendationResponseDto; +import com.petmatz.domain.user.response.UpdateRecommendationResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class RecommendComponent { + + private final JwtExtractProvider jwtExtractProvider; + private final UserRepository userRepository; + private final RecommendationRepository recommendationRepository; + + + @Transactional + public ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto) { + try { + Long recommendedId = dto.getUserId(); + Long myId = jwtExtractProvider.findIdFromJwt(); + User recommendedUser = userRepository.findById(recommendedId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + recommendedId)); + + boolean exists = userRepository.existsById(recommendedId); + if (!exists) { + return UpdateRecommendationResponseDto.userNotFound(); + } + Integer recommendationCount = recommendedUser.getRecommendationCount() + 1; + + recommendedUser.updateRecommendation(recommendationCount); + + Recommendation recommendation = UserFactory.createRecommendation(myId, recommendedId); + recommendationRepository.save(recommendation); + + } catch (Exception e) { + log.info("추천수 업데이트 실패: {}", e); + return UpdateRecommendationResponseDto.userNotFound(); + } + return UpdateRecommendationResponseDto.success(); + } + + public ResponseEntity getRecommend(UpdateRecommendationRequestDto dto) { + try { + Long recommendedId = dto.getUserId(); + Long myId = jwtExtractProvider.findIdFromJwt(); + + boolean exists = recommendationRepository.existsByMyIdAndRecommendedId(myId,recommendedId); + return GetRecommendationResponseDto.success(exists); + + } catch (Exception e) { + log.info("추천수 업데이트 실패: {}", e); + return GetRecommendationResponseDto.userNotFound(); + } + } + +} diff --git a/src/main/java/com/petmatz/domain/user/component/UserComponent.java b/src/main/java/com/petmatz/domain/user/component/UserComponent.java new file mode 100644 index 0000000..56d0f2c --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/UserComponent.java @@ -0,0 +1,87 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.api.user.request.DeleteIdRequestDto; +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.domain.pet.component.PetReader; +import com.petmatz.domain.pet.entity.Pet; +import com.petmatz.domain.pet.repository.PetRepository; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.UpdateLocationInfo; +import com.petmatz.domain.user.info.UserInfo; +import com.petmatz.domain.user.repository.CertificationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.DeleteIdResponseDto; +import com.petmatz.domain.user.response.UpdateLocationResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@Slf4j +public class UserComponent { + + /** + * pet 레포에서 꺼내오는 거 펫 도메인으로 옮겨야함. + */ + + private final UserRepository userRepository; + private final CertificationRepository certificationRepository; + private final JwtExtractProvider jwtExtractProvider; + private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + private final PetRepository petRepository; + + + @Transactional + public ResponseEntity deleteId(DeleteIdRequestDto dto) { + try { + Long userId = jwtExtractProvider.findIdFromJwt(); + boolean exists = userRepository.existsById(userId); + if (!exists) { + return DeleteIdResponseDto.idNotFound(); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + + // 비밀번호 일치 확인 + String password = dto.getPassword(); + String encodedPassword = user.getPassword(); + boolean isMatched = passwordEncoder.matches(password, encodedPassword); + if (!isMatched) return DeleteIdResponseDto.wrongPassword(); // 비밀번호 불일치 + + certificationRepository.deleteById(userId); + // 사용자 삭제 + List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 + // 명시적으로 Pet 삭제 + petRepository.deleteAll(pets); + userRepository.delete(user); + } catch (Exception e) { + log.info("회원 삭제 실패: {}", e); + return DeleteIdResponseDto.databaseError(); // 데이터베이스 오류 처리 + } + return DeleteIdResponseDto.success(); // 삭제 성공 응답 + } + + + public void deleteUser(Long userUUID) { + userRepository.deleteUserById(userUUID); + } + + public UserInfo selectUserInfo(String receiverEmail) { + User otherUser = userRepository.findByAccountId(receiverEmail); + return otherUser.of(); + } + + public String findByUserEmail(Long userId) { + return userRepository.findById(userId).get().getAccountId(); + } +} From 9f5772e7ac42f1e1dc6c8efb5c74478e145feec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Sat, 4 Jan 2025 18:08:41 +0900 Subject: [PATCH 02/13] =?UTF-8?q?Refactor=20:=20AuthService=EC=97=90=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=EA=B4=80=EB=A0=A8=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EC=99=80=20=EB=B6=84=EB=A6=AC=20(=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC,=20=EC=BF=A0=ED=82=A4=EC=84=B8=ED=8C=85=20=EB=AF=B8?= =?UTF-8?q?=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/user/controller/UserController.java | 10 +- .../domain/user/component/AuthComponent.java | 192 ------------------ .../domain/user/component/AuthService.java | 116 +++++++++++ .../component/AuthenticationComponent.java | 108 ++++++++++ .../GeocodingService.java | 19 +- .../domain/user/component/KaKaoComponent.java | 7 +- .../user/component/LocationComponent.java | 1 - .../com/petmatz/domain/user/entity/User.java | 2 +- .../domain/user/exception/MatchErrorCode.java | 10 +- ...MatchException.java => UserException.java} | 6 +- .../user/response/SignInResponseDto.java | 12 +- .../user/response/SignUpResponseDto.java | 2 +- .../domain/user/service/UserServiceImpl.java | 1 + 13 files changed, 266 insertions(+), 220 deletions(-) delete mode 100644 src/main/java/com/petmatz/domain/user/component/AuthComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/AuthService.java create mode 100644 src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java rename src/main/java/com/petmatz/domain/user/{service => component}/GeocodingService.java (83%) rename src/main/java/com/petmatz/domain/user/exception/{MatchException.java => UserException.java} (56%) diff --git a/src/main/java/com/petmatz/api/user/controller/UserController.java b/src/main/java/com/petmatz/api/user/controller/UserController.java index b9086bb..ef8946f 100644 --- a/src/main/java/com/petmatz/api/user/controller/UserController.java +++ b/src/main/java/com/petmatz/api/user/controller/UserController.java @@ -1,6 +1,7 @@ package com.petmatz.api.user.controller; import com.petmatz.api.user.request.*; +import com.petmatz.domain.user.component.AuthService; import com.petmatz.domain.user.response.*; import com.petmatz.domain.user.service.UserService; import com.petmatz.user.common.LogInResponseDto; @@ -11,12 +12,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.net.MalformedURLException; + @Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/api/auth") public class UserController { private final UserService userService; + private final AuthService authService; @PostMapping("/email-certification") public ResponseEntity emailCertification(@RequestBody @Valid EmailCertificationRequestDto requestBody) { @@ -33,10 +37,10 @@ public ResponseEntity checkCertification( } @PostMapping("/sign-up") - public ResponseEntity signUp(@RequestBody @Valid SignUpRequestDto requestBody) { - ResponseEntity response = userService.signUp(SignUpRequestDto.of(requestBody)); + public ResponseEntity signUp(@RequestBody @Valid SignUpRequestDto requestBody) throws MalformedURLException { + SignUpResponseDto responseDto = authService.signUp(SignUpRequestDto.of(requestBody)); log.info("[signUp]: { accountId: " + requestBody.getAccountId() + ", password: " + requestBody.getPassword()); - return response; + return ResponseEntity.ok(responseDto); } @PostMapping("/sign-in") diff --git a/src/main/java/com/petmatz/domain/user/component/AuthComponent.java b/src/main/java/com/petmatz/domain/user/component/AuthComponent.java deleted file mode 100644 index f03ed58..0000000 --- a/src/main/java/com/petmatz/domain/user/component/AuthComponent.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.petmatz.domain.user.component; - -import com.petmatz.common.security.utils.JwtProvider; -import com.petmatz.domain.aws.AwsClient; -import com.petmatz.domain.aws.vo.S3Imge; -import com.petmatz.domain.user.entity.Certification; -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.entity.UserFactory; -import com.petmatz.domain.user.info.CheckCertificationInfo; -import com.petmatz.domain.user.info.SignInInfo; -import com.petmatz.domain.user.info.SignUpInfo; -import com.petmatz.domain.user.repository.CertificationRepository; -import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.CheckCertificationResponseDto; -import com.petmatz.domain.user.response.SignInResponseDto; -import com.petmatz.domain.user.response.SignUpResponseDto; -import com.petmatz.domain.user.service.GeocodingService; -import com.petmatz.user.common.LogInResponseDto; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; - -@Component -@RequiredArgsConstructor -@Slf4j -public class AuthComponent { - - /** - * awsClient -> 추후에 infra 로 옮길 예정 - * 현재 1차로 분리작업만 하는중 - */ - - private final UserRepository userRepository; - private final CertificationRepository certificationRepository; - private final GeocodingService geocodingService; - private final AwsClient awsClient; // 추후에 수정 - private final JwtProvider jwtProvider; - private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - - @Transactional - public ResponseEntity signUp(SignUpInfo info) { - try { - String accountId = info.getAccountId(); - String certificationNumber = info.getCertificationNumber(); - - // 1. 필수 정보 누락 확인 - if (accountId == null || certificationNumber == null || info.getPassword() == null) { - return SignUpResponseDto.missingRequiredFields(); - } - - // 2. 인증 번호 확인 - Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); - if (certification == null || !certification.getIsVerified()) { - return SignUpResponseDto.certificationFail(); // 인증되지 않은 경우 - } - - // 3. 중복된 ID 확인 - if (userRepository.existsByAccountId(accountId)) { - return SignUpResponseDto.duplicateId(); - } - - // 5. 비밀번호 암호화 후 저장 - String encodedPassword = passwordEncoder.encode(info.getPassword()); - - // 6. GeocodingService를 통해 지역명과 6자리 행정코드 가져오기 - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); - if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - return SignUpResponseDto.locationFail(); - } - - //6-1 Img 정제 - S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); - - // 7. 새로운 User 생성 및 저장 - User user = UserFactory.createNewUser(info, encodedPassword, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger(), petImg.uploadURL()); - userRepository.save(user); - - // 8. 인증 엔티티 삭제 - certificationRepository.deleteAllByAccountId(accountId); - - // 9. 성공 응답 반환 - return SignUpResponseDto.success(user.getId(), petImg.checkResultImg()); - - } catch (RuntimeException e) { - log.error("회원 가입 실패: {}", e.getMessage(), e); - throw e; - } catch (Exception e) { - log.error("회원 가입 중 처리되지 않은 예외 발생: {}", e.getMessage(), e); - return SignUpResponseDto.unknownError(); - } - } - - - public ResponseEntity signIn(SignInInfo info, HttpServletResponse response) { - try { - String accountId = info.getAccountId(); - User user = userRepository.findByAccountId(accountId); - // 사용자 존재 여부 확인 - if (user == null) { - log.info("사용자 조회 실패: {}", accountId); - return SignInResponseDto.signInFail(); - } - - // 비밀번호 확인 - String password = info.getPassword(); - String encodedPassword = user.getPassword(); - if (!passwordEncoder.matches(password, encodedPassword)) { - log.info("비밀번호 불일치: {}", accountId); - return SignInResponseDto.signInFail(); - } - - // JWT 생성 (userId를 subject로, accountId를 클레임으로 설정) - String token = jwtProvider.create(user.getId(), user.getAccountId()); - log.info("JWT 생성 완료: {}", token); - - ResponseCookie responseCookie = ResponseCookie.from("jwt", token) - .httpOnly(true) // XSS 방지 - .secure(true) // HTTPS만 허용 - .path("/") // 모든 경로에서 접근 가능 - .sameSite("None") // SameSite=None 설정 - .maxAge((3600)) - .build(); - response.addHeader("Set-Cookie", responseCookie.toString()); - // 로그인 성공 응답 반환 - return SignInResponseDto.success(user); // User 객체 전달 - } catch (Exception e) { - log.error("로그인 처리 중 예외 발생", e); - return SignInResponseDto.signInFail(); - } - } - - public ResponseEntity logout(HttpServletResponse response) { - try { - // 만료된 쿠키 설정 - ResponseCookie expiredCookie = ResponseCookie.from("jwt", "") - .httpOnly(true) // XSS 방지 - .secure(true) // HTTPS만 허용 - .path("/") // 모든 경로에서 접근 가능 - .sameSite("None") // SameSite=None 설정 - .maxAge(0) // 즉시 만료 - .build(); - - response.addHeader("Set-Cookie", expiredCookie.toString()); - - log.info("JWT 쿠키 제거 및 로그아웃 처리 완료"); - return LogInResponseDto.success(); - } catch (Exception e) { - log.error("로그아웃 처리 중 예외 발생", e); - return LogInResponseDto.validationFail(); - } - } - - @Transactional - public ResponseEntity checkCertification(CheckCertificationInfo info) { - try { - String accountId = info.getAccountId(); - String certificationNumber = info.getCertificationNumber(); - - Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); - if (certification == null) return CheckCertificationResponseDto.certificationFail(); - - boolean isMatch = certification.getAccountId().equals(accountId) - && certification.getCertificationNumber().equals(certificationNumber); - - if (!isMatch) return CheckCertificationResponseDto.certificationFail(); - - // 인증 시간 검증 - LocalDateTime createdAt = certification.getCreatedAt(); - if (createdAt.isBefore(LocalDateTime.now().minusMinutes(5))) { // 5분 유효시간 - return CheckCertificationResponseDto.certificationExpired(); - } - - // 인증 완료 상태 업데이트 - certification.markAsVerified(); - certificationRepository.save(certification); - - } catch (Exception e) { - log.info("인증 번호 확인 실패: {}", e); - return CheckCertificationResponseDto.databaseError(); - } - - return CheckCertificationResponseDto.success(); - } -} diff --git a/src/main/java/com/petmatz/domain/user/component/AuthService.java b/src/main/java/com/petmatz/domain/user/component/AuthService.java new file mode 100644 index 0000000..1293721 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/AuthService.java @@ -0,0 +1,116 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.domain.aws.AwsClient; +import com.petmatz.domain.aws.vo.S3Imge; +import com.petmatz.domain.user.entity.Certification; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.entity.UserFactory; +import com.petmatz.domain.user.info.CheckCertificationInfo; +import com.petmatz.domain.user.info.SignInInfo; +import com.petmatz.domain.user.info.SignUpInfo; +import com.petmatz.domain.user.repository.CertificationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.CheckCertificationResponseDto; +import com.petmatz.domain.user.response.SignInResponseDto; +import com.petmatz.domain.user.response.SignUpResponseDto; +import com.petmatz.user.common.LogInResponseDto; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import javax.naming.AuthenticationException; +import java.net.MalformedURLException; +import java.security.cert.CertificateException; +import java.time.LocalDateTime; + +@Component +@RequiredArgsConstructor +@Slf4j +public class AuthService { + + /** + * awsClient -> 추후에 infra 로 옮길 예정 + * 현재 1차로 분리작업만 하는중 + */ + + private final UserRepository userRepository; + private final CertificationRepository certificationRepository; + private final GeocodingService geocodingService; + private final AwsClient awsClient; // 추후에 수정 + private final JwtProvider jwtProvider; + private final AuthenticationComponent authenticationComponent; + + private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Transactional + public SignUpResponseDto signUp(SignUpInfo info) throws MalformedURLException { + String accountId = info.getAccountId(); + String certificationNumber = info.getCertificationNumber(); + String password = info.getPassword(); + + authenticationComponent.validateRequiredFields(accountId, certificationNumber, password); + authenticationComponent.validateCertification(accountId); + authenticationComponent.validateDuplicateAccountId(accountId); + + String encodedPassword = passwordEncoder.encode(info.getPassword()); + + // 지역명과 6자리 행정코드 가져오기 + GeocodingService.KakaoRegion kakaoRegion = geocodingService.getValidRegion(info.getLatitude(), info.getLongitude()); + + //6-1 Img 정제 + S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); + + // 7. 새로운 User 생성 및 저장 + User user = UserFactory.createNewUser(info, encodedPassword, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger(), petImg.uploadURL()); + userRepository.save(user); + + // 8. 인증 엔티티 삭제 + certificationRepository.deleteAllByAccountId(accountId); + + // 9. 성공 응답 반환 + return new SignUpResponseDto(user.getId(), petImg.checkResultImg()); + } + + + public SignInResponseDto signIn(SignInInfo info, HttpServletResponse response) throws AuthenticationException { + User user = authenticationComponent.validateSignInCredentials(info); + String token = authenticationComponent.createJwtToken(user); + + ResponseCookie responseCookie = ResponseCookie.from("jwt", token) + .httpOnly(true) // XSS 방지 + .secure(true) // HTTPS만 허용 + .path("/") // 모든 경로에서 접근 가능 + .sameSite("None") // SameSite=None 설정 + .maxAge((3600)) + .build(); + response.addHeader("Set-Cookie", responseCookie.toString()); + // 로그인 성공 응답 반환 + return new SignInResponseDto(user); + } + + + public void logout(HttpServletResponse response) { // 예외 처리 + // 만료된 쿠키 설정 + ResponseCookie expiredCookie = ResponseCookie.from("jwt", "") + .httpOnly(true) // XSS 방지 + .secure(true) // HTTPS만 허용 + .path("/") // 모든 경로에서 접근 가능 + .sameSite("None") // SameSite=None 설정 + .maxAge(0) // 즉시 만료 + .build(); + response.addHeader("Set-Cookie", expiredCookie.toString()); + } + + @Transactional + public void checkCertification(CheckCertificationInfo info) throws CertificateException { + Certification certification = authenticationComponent.validateCertification(info); + authenticationComponent.updateCertificationStatus(certification); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java new file mode 100644 index 0000000..381b441 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java @@ -0,0 +1,108 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtProvider; +import com.petmatz.domain.user.entity.Certification; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.exception.UserException; +import com.petmatz.domain.user.info.CheckCertificationInfo; +import com.petmatz.domain.user.info.SignInInfo; +import com.petmatz.domain.user.repository.CertificationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import javax.naming.AuthenticationException; +import java.security.cert.CertificateException; +import java.time.LocalDateTime; + +import static com.petmatz.domain.user.exception.MatchErrorCode.CERTIFICATION_EXPIRED; +import static com.petmatz.domain.user.exception.MatchErrorCode.MISS_MATCH_CODE; + +@Component +@RequiredArgsConstructor +public class AuthenticationComponent { + + private final CertificationRepository certificationRepository; + private final UserRepository userRepository; + private final JwtProvider jwtProvider; + private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + public User validateSignInCredentials(SignInInfo info) throws AuthenticationException { + String accountId = info.getAccountId(); + String password = info.getPassword(); + + User user = userRepository.findByAccountId(accountId); + if (user == null) { + throw new AuthenticationException("사용자 조회 실패 "); + } + + String encodedPassword = user.getPassword(); + if (!passwordEncoder.matches(password, encodedPassword)) { + throw new AuthenticationException("비밀번호 불일치"); + } + return user; + } + + public String createJwtToken(User user) { + return jwtProvider.create(user.getId(), user.getAccountId()); + } + + + /** + * 필수 정보 누락 확인 + */ + public void validateRequiredFields(String accountId, String certificationNumber, String password) { + if (accountId == null || certificationNumber == null || password == null) { + throw new IllegalArgumentException("필수 정보가 누락되었습니다."); + } + } + + /** + * 인증 번호 확인 + */ + public void validateCertification(String accountId) { + Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); + if (certification == null || !certification.getIsVerified()) { + throw new IllegalStateException("인증 번호가 유효하지 않거나 인증되지 않았습니다."); + } + } + + /** + * 중복된 ID 확인 + */ + public void validateDuplicateAccountId(String accountId) { + if (userRepository.existsByAccountId(accountId)) { + throw new IllegalArgumentException("중복된 ID가 존재합니다."); + } + } + + /** + * 예외는 추후에 수정 (일단은 임시로 userEx 사용) + */ + public Certification validateCertification(CheckCertificationInfo info) throws CertificateException { + Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(info.getAccountId()); + if (certification == null) { + throw new CertificateException("인증 정보가 없습니다."); + } + // 인증 번호와 계정 ID 일치 여부 확인 + boolean isMatch = certification.getAccountId().equals(info.getAccountId()) + && certification.getCertificationNumber().equals(info.getCertificationNumber()); + + if (!isMatch) { + throw new UserException(MISS_MATCH_CODE); + } + + // 인증 시간 확인 + if (certification.getCreatedAt().isBefore(LocalDateTime.now().minusMinutes(5))) { + throw new UserException(CERTIFICATION_EXPIRED); + } + return certification; + } + + public void updateCertificationStatus(Certification certification) { + certification.markAsVerified(); + certificationRepository.save(certification); + } +} diff --git a/src/main/java/com/petmatz/domain/user/service/GeocodingService.java b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java similarity index 83% rename from src/main/java/com/petmatz/domain/user/service/GeocodingService.java rename to src/main/java/com/petmatz/domain/user/component/GeocodingService.java index e7b1259..9cada4d 100644 --- a/src/main/java/com/petmatz/domain/user/service/GeocodingService.java +++ b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java @@ -1,6 +1,7 @@ -package com.petmatz.domain.user.service; +package com.petmatz.domain.user.component; import com.fasterxml.jackson.annotation.JsonProperty; +import com.petmatz.domain.match.exception.MatchException; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; @@ -9,6 +10,8 @@ import java.util.List; +import static com.petmatz.domain.user.exception.MatchErrorCode.INSUFFICIENT_LOCATION_DATA; + @Service public class GeocodingService { @@ -57,6 +60,20 @@ private void logInfo(KakaoRegion region) { System.out.println("Region Code: " + region.getCode()); } + /** + * 좌표를 기반으로 지역 정보를 가져오고 유효성을 검증 + */ + public GeocodingService.KakaoRegion getValidRegion(double latitude, double longitude) { + GeocodingService.KakaoRegion kakaoRegion = getRegionFromCoordinates(latitude, longitude); + + // 지역 정보 검증 + if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { + throw new MatchException(INSUFFICIENT_LOCATION_DATA); + } + + return kakaoRegion; + } + @Data static class KakaoGeocodingResponse { private List documents; diff --git a/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java b/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java index d006199..95dfc3b 100644 --- a/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java @@ -1,10 +1,11 @@ package com.petmatz.domain.user.component; +import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.info.EditKakaoProfileInfo; +import com.petmatz.domain.user.repository.UserRepository; import com.petmatz.domain.user.response.EditKakaoProfileResponseDto; import com.petmatz.domain.user.response.UpdateLocationResponseDto; -import com.petmatz.domain.user.service.GeocodingService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -16,6 +17,10 @@ @Slf4j public class KaKaoComponent { + private final JwtExtractProvider jwtExtractProvider; + private final UserRepository userRepository; + private final GeocodingService geocodingService; + @Transactional public ResponseEntity editKakaoProfile(EditKakaoProfileInfo info) { try { diff --git a/src/main/java/com/petmatz/domain/user/component/LocationComponent.java b/src/main/java/com/petmatz/domain/user/component/LocationComponent.java index d7ac3ff..508e571 100644 --- a/src/main/java/com/petmatz/domain/user/component/LocationComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/LocationComponent.java @@ -5,7 +5,6 @@ import com.petmatz.domain.user.info.UpdateLocationInfo; import com.petmatz.domain.user.repository.UserRepository; import com.petmatz.domain.user.response.UpdateLocationResponseDto; -import com.petmatz.domain.user.service.GeocodingService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/petmatz/domain/user/entity/User.java b/src/main/java/com/petmatz/domain/user/entity/User.java index 88e35be..d4e9361 100644 --- a/src/main/java/com/petmatz/domain/user/entity/User.java +++ b/src/main/java/com/petmatz/domain/user/entity/User.java @@ -154,7 +154,7 @@ public UserInfo of() { .build(); } - public void checkLatitudeLongitude() { // (희수 : 예외나 위도 경도 범위 추후에 변경 예정입니다!) + public void checkLatitudeLongitude() { if (latitude <= 0) { throw new MatchException(INSUFFICIENT_LATITUDE_DATA); } diff --git a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java index f1245aa..f30dc45 100644 --- a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java +++ b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java @@ -6,18 +6,16 @@ @RequiredArgsConstructor public enum MatchErrorCode implements BaseErrorCode { - INSUFFICIENT_CARE_DATA(400, "INSUFFICIENT_CARE_DATA", "돌봄 여부가 없습니다."), - INSUFFICIENT_MBTI_DATA(400, "INSUFFICIENT_MBTI_DATA", "MBTI 별 점수를 찾을 수 없습니다."), + + INSUFFICIENT_LOCATION_DATA(404, "INSUFFICIENT_LOCATION_DATA", "유요한 위치 정보를 가져올 수 없습니다."), INSUFFICIENT_LATITUDE_DATA(400, "INSUFFICIENT_LATITUDE_DATA", "위도가 없습니다."), INSUFFICIENT_LONGITUDE_DATA(400, "INSUFFICIENT_LONGITUDE_DATA", "경도가 없습니다."), - INSUFFICIENT_TARGET_LATITUDE_DATA(400, "INSUFFICIENT_TARGET_LATITUDE_DATA", "타켓 유저의 위도가 없습니다."), - INSUFFICIENT_TARGET_LONGITUDE_DATA(400, "INSUFFICIENT_TARGET_LONGITUDE_DATA", "타켓 유저의 경도가 없습니다."), INVALID_MATCH_DATA(400, "INVALID_MATCH_DATA", "위도 또는 경도가 누락되었습니다."), NULL_PREFERRED_SIZES(400, "NULL_PREFERRED_SIZES", "선호 크기 목록이 없습니다."), NULL_TARGET_SIZE(400, "NULL_TARGET_SIZE", "타겟 크기가 없습니다."), NULL_MATCH_DATA(204, "NULL_MATCH_DATA", "매칭 데이터가 없습니다."), - INVALID_REDIS_DATA(404, "INVALID_REDIS_DATA", "redis 데이터를 불러올 수 없습니다."), - TARGET_MBTI_EMPTY(400, "TARGET_MBTI_EMPTY", "상대방의 mbti가 없습니다."); + CERTIFICATION_EXPIRED(400, "CERTIFICATION_EXPIRED", "인증 시간이 만료되었습니다."), + MISS_MATCH_CODE(400, "MISS_MATCH_CODE", "인증 번호가 일치하지 않습니다."); diff --git a/src/main/java/com/petmatz/domain/user/exception/MatchException.java b/src/main/java/com/petmatz/domain/user/exception/UserException.java similarity index 56% rename from src/main/java/com/petmatz/domain/user/exception/MatchException.java rename to src/main/java/com/petmatz/domain/user/exception/UserException.java index 30b8af0..cf4bfb9 100644 --- a/src/main/java/com/petmatz/domain/user/exception/MatchException.java +++ b/src/main/java/com/petmatz/domain/user/exception/UserException.java @@ -3,12 +3,12 @@ import com.petmatz.common.exception.BaseErrorCode; import com.petmatz.common.exception.DomainException; -public class MatchException extends DomainException { - public MatchException(BaseErrorCode errorCode, String message) { +public class UserException extends DomainException { + public UserException(BaseErrorCode errorCode, String message) { super(errorCode, message); } - public MatchException(BaseErrorCode errorCode) { + public UserException(BaseErrorCode errorCode) { super(errorCode); } } diff --git a/src/main/java/com/petmatz/domain/user/response/SignInResponseDto.java b/src/main/java/com/petmatz/domain/user/response/SignInResponseDto.java index a1e32bb..1ab41fc 100644 --- a/src/main/java/com/petmatz/domain/user/response/SignInResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/SignInResponseDto.java @@ -31,7 +31,7 @@ public class SignInResponseDto extends LogInResponseDto { private String region; private Integer regionCode; - private SignInResponseDto(User user) { + public SignInResponseDto(User user) { super(); this.id = user.getId(); this.accountId = user.getAccountId(); @@ -48,14 +48,4 @@ private SignInResponseDto(User user) { this.region = user.getRegion(); this.regionCode=user.getRegionCode(); } - - public static ResponseEntity success(User user) { - SignInResponseDto responseBody = new SignInResponseDto(user); - return ResponseEntity.status(HttpStatus.OK).body(responseBody); - } - - public static ResponseEntity signInFail() { - LogInResponseDto responseBody = new LogInResponseDto(ResponseCode.SIGN_IN_FAIL, ResponseMessage.SIGN_IN_FAIL); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(responseBody); - } } \ No newline at end of file diff --git a/src/main/java/com/petmatz/domain/user/response/SignUpResponseDto.java b/src/main/java/com/petmatz/domain/user/response/SignUpResponseDto.java index 196babc..66ea037 100644 --- a/src/main/java/com/petmatz/domain/user/response/SignUpResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/SignUpResponseDto.java @@ -13,7 +13,7 @@ public class SignUpResponseDto extends LogInResponseDto { private String imgURL; - private SignUpResponseDto(Long id, String imgURL) { + public SignUpResponseDto(Long id, String imgURL) { super(); this.id=id; this.imgURL = imgURL; diff --git a/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java b/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java index 419bb3c..51b5393 100644 --- a/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java @@ -7,6 +7,7 @@ import com.petmatz.domain.aws.vo.S3Imge; import com.petmatz.domain.pet.entity.Pet; import com.petmatz.domain.pet.repository.PetRepository; +import com.petmatz.domain.user.component.GeocodingService; import com.petmatz.domain.user.entity.*; import com.petmatz.domain.user.info.*; import com.petmatz.domain.user.provider.CertificationNumberProvider; From cacf56f302ca1737467e21799d20307c7b03e333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Mon, 6 Jan 2025 18:10:07 +0900 Subject: [PATCH 03/13] =?UTF-8?q?Refactor=20:=20Oauth=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20+=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EC=98=80=EC=9D=8C.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CustomOAuth2UserService.java | 26 +++ .../domain/user/component/EmailComponent.java | 53 ++----- .../user/component/GeocodingService.java | 3 +- .../domain/user/component/KaKaoComponent.java | 56 ------- .../domain/user/component/KakaoComponent.java | 63 ++++++++ .../user/component/KakaoUserComponent.java | 71 +++++++++ .../user/component/OAuth2UserLoader.java | 11 ++ .../com/petmatz/domain/user/entity/User.java | 3 + .../domain/user/exception/MatchErrorCode.java | 9 +- .../response/EditKakaoProfileResponseDto.java | 3 +- .../EmailCertificationResponseDto.java | 3 +- .../user/service/CustomOAuthUserService.java | 150 +++++++++--------- 12 files changed, 272 insertions(+), 179 deletions(-) create mode 100644 src/main/java/com/petmatz/domain/user/component/CustomOAuth2UserService.java delete mode 100644 src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/KakaoComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java create mode 100644 src/main/java/com/petmatz/domain/user/component/OAuth2UserLoader.java diff --git a/src/main/java/com/petmatz/domain/user/component/CustomOAuth2UserService.java b/src/main/java/com/petmatz/domain/user/component/CustomOAuth2UserService.java new file mode 100644 index 0000000..f43c1c4 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/CustomOAuth2UserService.java @@ -0,0 +1,26 @@ +package com.petmatz.domain.user.component; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService { + + private final List loaders; + + public OAuth2User loadUser(OAuth2UserRequest request) { + String registrationId = request.getClientRegistration().getRegistrationId(); + + return loaders.stream() + .filter(loader -> loader.supports(registrationId)) + .findFirst() + .orElseThrow(() -> new OAuth2AuthenticationException("지원되지 않는 id입니다 : " + registrationId)) + .loadUser(request); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/EmailComponent.java b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java index 9de0423..7b62bff 100644 --- a/src/main/java/com/petmatz/domain/user/component/EmailComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java @@ -1,59 +1,34 @@ package com.petmatz.domain.user.component; -import com.petmatz.api.user.request.EmailCertificationRequestDto; import com.petmatz.domain.user.entity.Certification; -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.provider.CertificationNumberProvider; +import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.repository.CertificationRepository; import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.EmailCertificationResponseDto; -import com.petmatz.domain.user.response.GetMyUserDto; -import com.petmatz.domain.user.service.EmailProvider; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import static com.petmatz.domain.user.exception.MatchErrorCode.USER_DUPLICATE; + @Component @RequiredArgsConstructor -@Slf4j public class EmailComponent { private final UserRepository userRepository; - private final EmailProvider emailProvider; private final CertificationRepository certificationRepository; - public ResponseEntity emailCertification(EmailCertificationRequestDto dto) { - try { - String accountId = dto.getAccountId(); - - //이메일 전송과 동시에 아이디 중복검사 - boolean isExistId = userRepository.existsByAccountId(accountId); - if (isExistId) return EmailCertificationResponseDto.duplicateId(); - - // 인증 번호 생성 및 이메일 전송 - String certificationNumber = CertificationNumberProvider.generateNumber(); - boolean isSendSuccess = emailProvider.sendVerificationEmail(accountId, certificationNumber); - if (!isSendSuccess) return EmailCertificationResponseDto.mailSendFail(); - - // 인증 엔티티 저장 - Certification certification = Certification.builder().accountId(accountId).certificationNumber(certificationNumber).isVerified(false).build(); - certificationRepository.save(certification); - - } catch (Exception e) { - log.info("이메일 인증 실패: {}", e); - return EmailCertificationResponseDto.databaseError(); + public void checkDuplicateAccountId(String accountId) { + if (userRepository.existsByAccountId(accountId)) { + throw new UserException(USER_DUPLICATE); } - return EmailCertificationResponseDto.success(); } - public GetMyUserDto receiverEmail(String accountId) { - try { - User user = userRepository.findByAccountId(accountId); - return new GetMyUserDto(user); - } catch (Exception e) { - e.printStackTrace(); - } - return new GetMyUserDto(); + public void saveCertification(String accountId, String certificationNumber) { + Certification certification = Certification.builder() + .accountId(accountId) + .certificationNumber(certificationNumber) + .isVerified(false) + .build(); + certificationRepository.save(certification); } + } diff --git a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java index 9cada4d..b45b374 100644 --- a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java +++ b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java @@ -15,7 +15,8 @@ @Service public class GeocodingService { - private final String KAKAO_API_URL = "https://dapi.kakao.com/v2/local/geo/coord2regioncode.json"; + @Value("${kakao-api-url}") + private String KAKAO_API_URL; @Value("${kakao-api-key}") private String KAKAO_API_KEY; diff --git a/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java b/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java deleted file mode 100644 index 95dfc3b..0000000 --- a/src/main/java/com/petmatz/domain/user/component/KaKaoComponent.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.petmatz.domain.user.component; - -import com.petmatz.common.security.utils.JwtExtractProvider; -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.info.EditKakaoProfileInfo; -import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.EditKakaoProfileResponseDto; -import com.petmatz.domain.user.response.UpdateLocationResponseDto; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -@Component -@RequiredArgsConstructor -@Slf4j -public class KaKaoComponent { - - private final JwtExtractProvider jwtExtractProvider; - private final UserRepository userRepository; - private final GeocodingService geocodingService; - - @Transactional - public ResponseEntity editKakaoProfile(EditKakaoProfileInfo info) { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - - // userId가 null인 경우 예외 처리 - if (userId == null) { - log.warn("JWT에서 추출된 userId가 null입니다."); - return EditKakaoProfileResponseDto.idNotFound(); - } - - boolean exists = userRepository.existsById(userId); - if (!exists) { - log.warn("존재하지 않는 사용자 ID: {}", userId); - return EditKakaoProfileResponseDto.idNotFound(); - } - - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); - if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 - } - - user.updateKakaoProfile(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - } catch (Exception e) { - log.error("프로필 수정 실패", e); - return EditKakaoProfileResponseDto.editFailed(); - } - return EditKakaoProfileResponseDto.success(); - } -} diff --git a/src/main/java/com/petmatz/domain/user/component/KakaoComponent.java b/src/main/java/com/petmatz/domain/user/component/KakaoComponent.java new file mode 100644 index 0000000..e6fbccd --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/KakaoComponent.java @@ -0,0 +1,63 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.domain.user.entity.CustomOAuthUser; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class KakaoComponent extends DefaultOAuth2UserService implements OAuth2UserLoader { + + private final UserRepository userRepository; + private final KakaoUserComponent kaKaoUserComponent; + @Override + public boolean supports(String registrationId) { + return "kakao".equalsIgnoreCase(registrationId); + } + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + Map attributes = oAuth2User.getAttributes(); + + // RegistrationId 체크 + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + if (!"kakao".equals(registrationId)) { + throw new OAuth2AuthenticationException("Unsupported registrationId: " + registrationId); + } + + // id 추출 + String kakaoAccountId = attributes.get("id").toString(); // 고유 ID + Map kakaoAccount = (Map) attributes.get("kakao_account"); + + // 이메일 추출 + String email = (String) kakaoAccount.get("email"); + if (email == null || email.isEmpty()) { + throw new OAuth2AuthenticationException("Email is required for Kakao login."); + } + + // 닉네임 추출 + Map profile = (Map) kakaoAccount.get("profile"); + String nickname = (String) profile.getOrDefault("nickname", "Unknown User"); + + // 이미지 추출 (선택) + String profileImage = (String) profile.getOrDefault("profile_image_url", ""); + + User user = userRepository.findByAccountId(email); + if (user == null) { + user = kaKaoUserComponent.createNewKakaoUser(kakaoAccountId, email, nickname, profileImage); + } + + return new CustomOAuthUser(user.getId(), user.getAccountId(), attributes, oAuth2User.getAuthorities()); + } + + +} diff --git a/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java b/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java new file mode 100644 index 0000000..e40032a --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java @@ -0,0 +1,71 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.constant.LoginRole; +import com.petmatz.domain.user.constant.LoginType; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.exception.UserException; +import com.petmatz.domain.user.info.EditKakaoProfileInfo; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.EditKakaoProfileResponseDto; +import com.petmatz.domain.user.response.UpdateLocationResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import static com.petmatz.domain.user.exception.MatchErrorCode.*; + +@Component +@RequiredArgsConstructor +@Slf4j +public class KakaoUserComponent { + + /** + * 카카오 프로필 수정 어디다가 넣을지 고민중,,, + */ + + private final JwtExtractProvider jwtExtractProvider; + private final UserRepository userRepository; + private final GeocodingService geocodingService; + + @Transactional + public void editKakaoProfile(EditKakaoProfileInfo info) { + Long userId = jwtExtractProvider.findIdFromJwt(); + + if (userId == null) { + throw new UserException(JWT_USER_NOT_FOUND); + } + + if (userRepository.existsById(userId)) { + throw new UserException(USER_DUPLICATE); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + + GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); + if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { + throw new UserException(MISS_KAKAO_LOACTION); + } + + user.updateKakaoProfile(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); + } + + public User createNewKakaoUser(String kakaoAccountId, String email, String nickname, String profileImage) { + User newUser = User.builder() + .accountId(email) // 이메일을 accountId에 저장 + .registrationId(kakaoAccountId) + .password("password") // 기본 비밀번호 설정 (필요시 변경) + .nickname(nickname) + .profileImg(profileImage) // 프로필 이미지 저장 + .loginRole(LoginRole.ROLE_USER) // 기본 역할 설정 + .loginType(LoginType.KAKAO) // 로그인 타입 + .careCompletionCount(0) + .recommendationCount(0) + .build(); + + return userRepository.save(newUser); + } +} diff --git a/src/main/java/com/petmatz/domain/user/component/OAuth2UserLoader.java b/src/main/java/com/petmatz/domain/user/component/OAuth2UserLoader.java new file mode 100644 index 0000000..bdb187a --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/OAuth2UserLoader.java @@ -0,0 +1,11 @@ +package com.petmatz.domain.user.component; + + +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public interface OAuth2UserLoader { + boolean supports(String registrationId); + OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException; +} diff --git a/src/main/java/com/petmatz/domain/user/entity/User.java b/src/main/java/com/petmatz/domain/user/entity/User.java index d4e9361..dbb16a8 100644 --- a/src/main/java/com/petmatz/domain/user/entity/User.java +++ b/src/main/java/com/petmatz/domain/user/entity/User.java @@ -42,6 +42,9 @@ public class User extends BaseEntity { @Column(name = "account_id",unique = true) private String accountId; + @Column(name = "registrationId") + private String registrationId; + @Column(name = "password") private String password; diff --git a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java index f30dc45..2ca8640 100644 --- a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java +++ b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java @@ -8,14 +8,15 @@ public enum MatchErrorCode implements BaseErrorCode { INSUFFICIENT_LOCATION_DATA(404, "INSUFFICIENT_LOCATION_DATA", "유요한 위치 정보를 가져올 수 없습니다."), + MISS_KAKAO_LOACTION(400, "MISS_KAKAO_LOACTION", "카카오 지역 api를 호출 할 수 없습니다."), INSUFFICIENT_LATITUDE_DATA(400, "INSUFFICIENT_LATITUDE_DATA", "위도가 없습니다."), INSUFFICIENT_LONGITUDE_DATA(400, "INSUFFICIENT_LONGITUDE_DATA", "경도가 없습니다."), INVALID_MATCH_DATA(400, "INVALID_MATCH_DATA", "위도 또는 경도가 누락되었습니다."), - NULL_PREFERRED_SIZES(400, "NULL_PREFERRED_SIZES", "선호 크기 목록이 없습니다."), - NULL_TARGET_SIZE(400, "NULL_TARGET_SIZE", "타겟 크기가 없습니다."), - NULL_MATCH_DATA(204, "NULL_MATCH_DATA", "매칭 데이터가 없습니다."), + USER_NOT_FOUND(404, "USER_NOT_FOUND", "사용자가 존재하지 않습니다."), + JWT_USER_NOT_FOUND(404, "JWT_USER_NOT_FOUND", "토큰에서 추출한 사용자가 존재하지 않습니다."), CERTIFICATION_EXPIRED(400, "CERTIFICATION_EXPIRED", "인증 시간이 만료되었습니다."), - MISS_MATCH_CODE(400, "MISS_MATCH_CODE", "인증 번호가 일치하지 않습니다."); + MISS_MATCH_CODE(400, "MISS_MATCH_CODE", "인증 번호가 일치하지 않습니다."), + USER_DUPLICATE(400, "USER_DUPLICATE", "중복된 사용자가 있습니다."); diff --git a/src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java b/src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java index b869f65..59cda5a 100644 --- a/src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/EditKakaoProfileResponseDto.java @@ -7,8 +7,7 @@ import org.springframework.http.ResponseEntity; public class EditKakaoProfileResponseDto extends LogInResponseDto { - private EditKakaoProfileResponseDto(){ - super(); + public EditKakaoProfileResponseDto(){ } public static ResponseEntity idNotFound(){ diff --git a/src/main/java/com/petmatz/domain/user/response/EmailCertificationResponseDto.java b/src/main/java/com/petmatz/domain/user/response/EmailCertificationResponseDto.java index 176d9eb..b75196b 100644 --- a/src/main/java/com/petmatz/domain/user/response/EmailCertificationResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/EmailCertificationResponseDto.java @@ -11,8 +11,7 @@ @Getter public class EmailCertificationResponseDto extends LogInResponseDto { - private EmailCertificationResponseDto(){ - super(); + public EmailCertificationResponseDto(){ } diff --git a/src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java b/src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java index e0a82ae..5ec17e2 100644 --- a/src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java +++ b/src/main/java/com/petmatz/domain/user/service/CustomOAuthUserService.java @@ -1,75 +1,75 @@ -package com.petmatz.domain.user.service; - -import com.petmatz.domain.user.constant.LoginRole; -import com.petmatz.domain.user.constant.LoginType; -import com.petmatz.domain.user.entity.CustomOAuthUser; -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; - -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class CustomOAuthUserService extends DefaultOAuth2UserService { - - private final UserRepository userRepository; - - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); - Map attributes = oAuth2User.getAttributes(); - - // 1. Check the registration ID (ensure it's Kakao) - String registrationId = userRequest.getClientRegistration().getRegistrationId(); - if (!"kakao".equals(registrationId)) { - throw new OAuth2AuthenticationException("Unsupported registrationId: " + registrationId); - } - - // 2. Extract attributes - String kakaoAccountId = attributes.get("id").toString(); // 고유 ID - Map kakaoAccount = (Map) attributes.get("kakao_account"); - - // Extract email (required for accountId) - String email = (String) kakaoAccount.get("email"); - if (email == null || email.isEmpty()) { - throw new OAuth2AuthenticationException("Email is required for Kakao login."); - } - - // Extract nickname - Map profile = (Map) kakaoAccount.get("profile"); - String nickname = (String) profile.getOrDefault("nickname", "Unknown User"); - - // Extract profile image (optional) - String profileImage = (String) profile.getOrDefault("profile_image_url", ""); - - // 3. Check if user exists or create new user - User user = userRepository.findByAccountId(email); - if (user == null) { - user = createNewKakaoUser(email, nickname, profileImage); // 신규 사용자 생성 - } - - // 4. Return CustomOAuthUser - return new CustomOAuthUser(user.getId(), user.getAccountId(), attributes, oAuth2User.getAuthorities()); - } - - private User createNewKakaoUser(String email, String nickname, String profileImage) { - User newUser = User.builder() - .accountId(email) // 이메일을 accountId에 저장 - .password("password") // 기본 비밀번호 설정 (필요시 변경) - .nickname(nickname) - .profileImg(profileImage) // 프로필 이미지 저장 - .loginRole(LoginRole.ROLE_USER) // 기본 역할 설정 - .loginType(LoginType.KAKAO) // 로그인 타입 - .careCompletionCount(0) - .recommendationCount(0) - .build(); - - return userRepository.save(newUser); - } -} \ No newline at end of file +//package com.petmatz.domain.user.service; +// +//import com.petmatz.domain.user.constant.LoginRole; +//import com.petmatz.domain.user.constant.LoginType; +//import com.petmatz.domain.user.entity.CustomOAuthUser; +//import com.petmatz.domain.user.entity.User; +//import com.petmatz.domain.user.repository.UserRepository; +//import lombok.RequiredArgsConstructor; +//import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +//import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +//import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +//import org.springframework.security.oauth2.core.user.OAuth2User; +//import org.springframework.stereotype.Service; +// +//import java.util.Map; +// +//@Service +//@RequiredArgsConstructor +//public class CustomOAuthUserService extends DefaultOAuth2UserService { +// +// private final UserRepository userRepository; +// +// @Override +// public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { +// OAuth2User oAuth2User = super.loadUser(userRequest); +// Map attributes = oAuth2User.getAttributes(); +// +// // 1. Check the registration ID (ensure it's Kakao) +// String registrationId = userRequest.getClientRegistration().getRegistrationId(); +// if (!"kakao".equals(registrationId)) { +// throw new OAuth2AuthenticationException("Unsupported registrationId: " + registrationId); +// } +// +// // 2. Extract attributes +// String kakaoAccountId = attributes.get("id").toString(); // 고유 ID +// Map kakaoAccount = (Map) attributes.get("kakao_account"); +// +// // Extract email (required for accountId) +// String email = (String) kakaoAccount.get("email"); +// if (email == null || email.isEmpty()) { +// throw new OAuth2AuthenticationException("Email is required for Kakao login."); +// } +// +// // Extract nickname +// Map profile = (Map) kakaoAccount.get("profile"); +// String nickname = (String) profile.getOrDefault("nickname", "Unknown User"); +// +// // Extract profile image (optional) +// String profileImage = (String) profile.getOrDefault("profile_image_url", ""); +// +// // 3. Check if user exists or create new user +// User user = userRepository.findByAccountId(email); +// if (user == null) { +// user = createNewKakaoUser(email, nickname, profileImage); // 신규 사용자 생성 +// } +// +// // 4. Return CustomOAuthUser +// return new CustomOAuthUser(user.getId(), user.getAccountId(), attributes, oAuth2User.getAuthorities()); +// } +// +// private User createNewKakaoUser(String email, String nickname, String profileImage) { +// User newUser = User.builder() +// .accountId(email) // 이메일을 accountId에 저장 +// .password("password") // 기본 비밀번호 설정 (필요시 변경) +// .nickname(nickname) +// .profileImg(profileImage) // 프로필 이미지 저장 +// .loginRole(LoginRole.ROLE_USER) // 기본 역할 설정 +// .loginType(LoginType.KAKAO) // 로그인 타입 +// .careCompletionCount(0) +// .recommendationCount(0) +// .build(); +// +// return userRepository.save(newUser); +// } +//} \ No newline at end of file From 6301ef736773e7cef3b67234cba7cd7aaec5830e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Mon, 6 Jan 2025 18:10:29 +0900 Subject: [PATCH 04/13] =?UTF-8?q?Refactor=20:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/component/EmailService.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/java/com/petmatz/domain/user/component/EmailService.java diff --git a/src/main/java/com/petmatz/domain/user/component/EmailService.java b/src/main/java/com/petmatz/domain/user/component/EmailService.java new file mode 100644 index 0000000..e83082d --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/EmailService.java @@ -0,0 +1,43 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.api.user.request.EmailCertificationRequestDto; +import com.petmatz.domain.user.entity.Certification; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.provider.CertificationNumberProvider; +import com.petmatz.domain.user.repository.CertificationRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.EmailCertificationResponseDto; +import com.petmatz.domain.user.response.GetMyUserDto; +import com.petmatz.domain.user.service.EmailProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class EmailService { + + private final UserRepository userRepository; + private final EmailProvider emailProvider; + private final EmailComponent emailComponent; + + + public void emailCertification(EmailCertificationRequestDto dto) { + String accountId = dto.getAccountId(); + + emailComponent.checkDuplicateAccountId(accountId); + + // 인증 번호 생성 및 이메일 전송 + String certificationNumber = CertificationNumberProvider.generateNumber(); + emailProvider.sendVerificationEmail(accountId, certificationNumber); + + emailComponent.saveCertification(accountId, certificationNumber); + } + + + public GetMyUserDto receiverEmail(String accountId) { + User user = userRepository.findByAccountId(accountId); + return new GetMyUserDto(user); + } +} From e9af37441277ea1b05b82d9ac4398af6bf698986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Wed, 8 Jan 2025 22:29:18 +0900 Subject: [PATCH 05/13] =?UTF-8?q?Refactor=20:=20HeartService=20|=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B9=84?= =?UTF-8?q?=EC=A7=80=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/component/GeocodingService.java | 7 +- .../domain/user/component/HeartComponent.java | 118 +++++++----------- .../domain/user/exception/MatchErrorCode.java | 3 +- 3 files changed, 50 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java index b45b374..f2d038a 100644 --- a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java +++ b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.petmatz.domain.match.exception.MatchException; +import com.petmatz.domain.user.exception.UserException; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; @@ -15,9 +16,10 @@ @Service public class GeocodingService { + + @Value("${kakao-api-url}") private String KAKAO_API_URL; - @Value("${kakao-api-key}") private String KAKAO_API_KEY; @@ -69,9 +71,8 @@ public GeocodingService.KakaoRegion getValidRegion(double latitude, double longi // 지역 정보 검증 if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - throw new MatchException(INSUFFICIENT_LOCATION_DATA); + throw new UserException(INSUFFICIENT_LOCATION_DATA); } - return kakaoRegion; } diff --git a/src/main/java/com/petmatz/domain/user/component/HeartComponent.java b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java index 8b1c57d..376aeaa 100644 --- a/src/main/java/com/petmatz/domain/user/component/HeartComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java @@ -1,104 +1,74 @@ package com.petmatz.domain.user.component; import com.petmatz.api.user.request.HeartedUserDto; -import com.petmatz.api.user.request.HeartingRequestDto; -import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.user.entity.Heart; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.entity.UserFactory; +import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.repository.HeartRepository; import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.GetHeartingListResponseDto; -import com.petmatz.domain.user.response.HeartingResponseDto; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; -@Component +import static com.petmatz.domain.user.exception.MatchErrorCode.*; + @RequiredArgsConstructor -@Slf4j +@Component public class HeartComponent { private final UserRepository userRepository; - private final JwtExtractProvider jwtExtractProvider; private final HeartRepository heartRepository; - @Transactional - public ResponseEntity hearting(HeartingRequestDto dto) { - try { - Long heartedId = dto.getHeartedId(); - - // 대상 사용자가 존재하는지 확인 - boolean exists = userRepository.existsById(heartedId); - if (!exists) { - return HeartingResponseDto.heartedIdNotFound(); - } - - // 현재 사용자 ID 가져오기 - Long userId = jwtExtractProvider.findIdFromJwt(); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - // DB에서 myId와 heartedId로 Heart 레코드 확인 - Optional existingHeart = heartRepository.findByMyIdAndHeartedId(user.getId(), heartedId); - - if (existingHeart.isPresent()) { - // 찜하기 해제 (DB에서 삭제) - heartRepository.delete(existingHeart.get()); - return HeartingResponseDto.success(); // 찜하기 해제 성공 응답 - } - - // 찜하기 진행 (DB에 저장) - Heart heart = UserFactory.createHeart(userId, heartedId); - heartRepository.save(heart); - - return HeartingResponseDto.success(); // 찜하기 성공 응답 - - } catch (Exception e) { - log.info("찜하기 실패: {}", e); - return HeartingResponseDto.databaseError(); // 데이터베이스 오류 응답 + public void validateHeartUser(Long heartId) { + if (!userRepository.existsById(heartId)) { + throw new UserException(HEART_USER_NOT_FOUND); } } + @Transactional + public User getCurrentUser(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + } - public ResponseEntity getHeartedList() { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - // Heart 리스트 조회 - List heartList = heartRepository.findAllByMyId(user.getId()); - - // Heart 정보와 관련된 User 데이터 매핑 - List heartedUsers = heartList.stream() - .map(heart -> { - User heartedUser = userRepository.findById(heart.getHeartedId()) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + heart.getHeartedId())); + @Transactional + public boolean toggleHeart(Long myId, Long heartId) { + Optional existingHeart = heartRepository.findByMyIdAndHeartedId(myId, heartId); - return new HeartedUserDto( - heart.getMyId(), - heart.getHeartedId(), - heartedUser.getNickname(), - heartedUser.getProfileImg(), - heartedUser.getCareAvailable(), - heartedUser.getPreferredSizes() - ); - }) - .toList(); + if (existingHeart.isPresent()) { + heartRepository.delete(existingHeart.get()); + return false; // 혜제 + } - // 성공 응답 반환 - return GetHeartingListResponseDto.success(heartedUsers); + Heart heart = UserFactory.createHeart(myId, heartId); + heartRepository.save(heart); + return true; + } - } catch (Exception e) { - log.info("찜리스트 받아오기 실패: {}", e); - return HeartingResponseDto.databaseError(); - } + @Transactional + public List getHeartedUsers(Long myId) { + + List heartList = heartRepository.findAllByMyId(myId); + + return heartList.stream() + .map(heart -> { + User heartedUser = userRepository.findById(heart.getHeartedId()) + .orElseThrow(() -> new UserException(HEART_USER_NOT_FOUND)); + + return new HeartedUserDto( + heart.getMyId(), + heart.getHeartedId(), + heartedUser.getNickname(), + heartedUser.getProfileImg(), + heartedUser.getCareAvailable(), + heartedUser.getPreferredSizes() + ); + }) + .toList(); } } diff --git a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java index 2ca8640..78e5b94 100644 --- a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java +++ b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java @@ -16,7 +16,8 @@ public enum MatchErrorCode implements BaseErrorCode { JWT_USER_NOT_FOUND(404, "JWT_USER_NOT_FOUND", "토큰에서 추출한 사용자가 존재하지 않습니다."), CERTIFICATION_EXPIRED(400, "CERTIFICATION_EXPIRED", "인증 시간이 만료되었습니다."), MISS_MATCH_CODE(400, "MISS_MATCH_CODE", "인증 번호가 일치하지 않습니다."), - USER_DUPLICATE(400, "USER_DUPLICATE", "중복된 사용자가 있습니다."); + USER_DUPLICATE(400, "USER_DUPLICATE", "중복된 사용자가 있습니다."), + HEART_USER_NOT_FOUND(404, "HEART_USER_DUPLICATE", "찜한 사용자를 찾을 수 없습니다."); From 23bc6b002c48937f640167098496f41718f69e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Wed, 8 Jan 2025 22:29:49 +0900 Subject: [PATCH 06/13] =?UTF-8?q?Refactor=20:=20HeartService=20|=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B9=84?= =?UTF-8?q?=EC=A7=80=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/component/HeartService.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/com/petmatz/domain/user/component/HeartService.java diff --git a/src/main/java/com/petmatz/domain/user/component/HeartService.java b/src/main/java/com/petmatz/domain/user/component/HeartService.java new file mode 100644 index 0000000..41340d9 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/HeartService.java @@ -0,0 +1,49 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.api.user.request.HeartedUserDto; +import com.petmatz.api.user.request.HeartingRequestDto; +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.entity.Heart; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.entity.UserFactory; +import com.petmatz.domain.user.repository.HeartRepository; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.GetHeartingListResponseDto; +import com.petmatz.domain.user.response.HeartingResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class HeartService { + + private final UserRepository userRepository; + private final JwtExtractProvider jwtExtractProvider; + private final HeartRepository heartRepository; + private final HeartComponent heartComponent; + + + @Transactional + public void hearting(HeartingRequestDto dto) { + Long heartedId = dto.getHeartedId(); + Long userId = jwtExtractProvider.findIdFromJwt(); + + heartComponent.validateHeartUser(heartedId); + User currentUser = heartComponent.getCurrentUser(userId); + heartComponent.toggleHeart(currentUser.getId(), heartedId); + } + + + public List getHeartedList() { + Long userId = jwtExtractProvider.findIdFromJwt(); + List heartedUsers = heartComponent.getHeartedUsers(userId); + return heartedUsers; + } +} From 9f540404ca6bc889330258bdace38dbfe9973872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Wed, 8 Jan 2025 23:00:23 +0900 Subject: [PATCH 07/13] =?UTF-8?q?Refactor=20:=20userUtils=20=EB=A1=9C=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=EC=9D=91=EB=8B=B5=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=AC=B6=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/AuthenticationComponent.java | 7 +-- .../domain/user/component/EmailComponent.java | 6 -- .../domain/user/component/EmailService.java | 9 +-- .../user/component/GeocodingService.java | 1 - .../domain/user/component/HeartComponent.java | 6 -- .../domain/user/component/HeartService.java | 11 +--- .../user/component/KakaoUserComponent.java | 15 ++--- .../domain/user/component/UserReader.java | 5 +- .../domain/user/component/UserUtils.java | 58 +++++++++++++++++++ 9 files changed, 73 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/petmatz/domain/user/component/UserUtils.java diff --git a/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java index 381b441..b55f55a 100644 --- a/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/AuthenticationComponent.java @@ -27,16 +27,14 @@ public class AuthenticationComponent { private final CertificationRepository certificationRepository; private final UserRepository userRepository; private final JwtProvider jwtProvider; + private final UserUtils userUtils; private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); public User validateSignInCredentials(SignInInfo info) throws AuthenticationException { String accountId = info.getAccountId(); String password = info.getPassword(); - User user = userRepository.findByAccountId(accountId); - if (user == null) { - throw new AuthenticationException("사용자 조회 실패 "); - } + User user = userUtils.findUser(accountId); String encodedPassword = user.getPassword(); if (!passwordEncoder.matches(password, encodedPassword)) { @@ -49,7 +47,6 @@ public String createJwtToken(User user) { return jwtProvider.create(user.getId(), user.getAccountId()); } - /** * 필수 정보 누락 확인 */ diff --git a/src/main/java/com/petmatz/domain/user/component/EmailComponent.java b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java index 7b62bff..04a250c 100644 --- a/src/main/java/com/petmatz/domain/user/component/EmailComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/EmailComponent.java @@ -16,12 +16,6 @@ public class EmailComponent { private final UserRepository userRepository; private final CertificationRepository certificationRepository; - public void checkDuplicateAccountId(String accountId) { - if (userRepository.existsByAccountId(accountId)) { - throw new UserException(USER_DUPLICATE); - } - } - public void saveCertification(String accountId, String certificationNumber) { Certification certification = Certification.builder() .accountId(accountId) diff --git a/src/main/java/com/petmatz/domain/user/component/EmailService.java b/src/main/java/com/petmatz/domain/user/component/EmailService.java index e83082d..150de9f 100644 --- a/src/main/java/com/petmatz/domain/user/component/EmailService.java +++ b/src/main/java/com/petmatz/domain/user/component/EmailService.java @@ -1,12 +1,9 @@ package com.petmatz.domain.user.component; import com.petmatz.api.user.request.EmailCertificationRequestDto; -import com.petmatz.domain.user.entity.Certification; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.provider.CertificationNumberProvider; -import com.petmatz.domain.user.repository.CertificationRepository; import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.EmailCertificationResponseDto; import com.petmatz.domain.user.response.GetMyUserDto; import com.petmatz.domain.user.service.EmailProvider; import lombok.RequiredArgsConstructor; @@ -18,15 +15,15 @@ @Slf4j public class EmailService { - private final UserRepository userRepository; private final EmailProvider emailProvider; private final EmailComponent emailComponent; + private final UserUtils userUtils; public void emailCertification(EmailCertificationRequestDto dto) { String accountId = dto.getAccountId(); - emailComponent.checkDuplicateAccountId(accountId); + userUtils.checkDuplicateAccountId(accountId); // 인증 번호 생성 및 이메일 전송 String certificationNumber = CertificationNumberProvider.generateNumber(); @@ -37,7 +34,7 @@ public void emailCertification(EmailCertificationRequestDto dto) { public GetMyUserDto receiverEmail(String accountId) { - User user = userRepository.findByAccountId(accountId); + User user = userUtils.findUser(accountId); return new GetMyUserDto(user); } } diff --git a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java index f2d038a..9cc748c 100644 --- a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java +++ b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java @@ -68,7 +68,6 @@ private void logInfo(KakaoRegion region) { */ public GeocodingService.KakaoRegion getValidRegion(double latitude, double longitude) { GeocodingService.KakaoRegion kakaoRegion = getRegionFromCoordinates(latitude, longitude); - // 지역 정보 검증 if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { throw new UserException(INSUFFICIENT_LOCATION_DATA); diff --git a/src/main/java/com/petmatz/domain/user/component/HeartComponent.java b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java index 376aeaa..90e9c71 100644 --- a/src/main/java/com/petmatz/domain/user/component/HeartComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/HeartComponent.java @@ -30,12 +30,6 @@ public void validateHeartUser(Long heartId) { } } - @Transactional - public User getCurrentUser(Long userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new UserException(USER_NOT_FOUND)); - } - @Transactional public boolean toggleHeart(Long myId, Long heartId) { Optional existingHeart = heartRepository.findByMyIdAndHeartedId(myId, heartId); diff --git a/src/main/java/com/petmatz/domain/user/component/HeartService.java b/src/main/java/com/petmatz/domain/user/component/HeartService.java index 41340d9..9ef4504 100644 --- a/src/main/java/com/petmatz/domain/user/component/HeartService.java +++ b/src/main/java/com/petmatz/domain/user/component/HeartService.java @@ -3,31 +3,24 @@ import com.petmatz.api.user.request.HeartedUserDto; import com.petmatz.api.user.request.HeartingRequestDto; import com.petmatz.common.security.utils.JwtExtractProvider; -import com.petmatz.domain.user.entity.Heart; import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.entity.UserFactory; import com.petmatz.domain.user.repository.HeartRepository; import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.GetHeartingListResponseDto; -import com.petmatz.domain.user.response.HeartingResponseDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Optional; @Component @RequiredArgsConstructor @Slf4j public class HeartService { - private final UserRepository userRepository; private final JwtExtractProvider jwtExtractProvider; - private final HeartRepository heartRepository; private final HeartComponent heartComponent; + private final UserUtils userUtils; @Transactional @@ -36,7 +29,7 @@ public void hearting(HeartingRequestDto dto) { Long userId = jwtExtractProvider.findIdFromJwt(); heartComponent.validateHeartUser(heartedId); - User currentUser = heartComponent.getCurrentUser(userId); + User currentUser = userUtils.getCurrentUser(userId); heartComponent.toggleHeart(currentUser.getId(), heartedId); } diff --git a/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java b/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java index e40032a..52cd98b 100644 --- a/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java @@ -29,21 +29,14 @@ public class KakaoUserComponent { private final JwtExtractProvider jwtExtractProvider; private final UserRepository userRepository; private final GeocodingService geocodingService; + private final UserUtils userUtils; @Transactional public void editKakaoProfile(EditKakaoProfileInfo info) { Long userId = jwtExtractProvider.findIdFromJwt(); - - if (userId == null) { - throw new UserException(JWT_USER_NOT_FOUND); - } - - if (userRepository.existsById(userId)) { - throw new UserException(USER_DUPLICATE); - } - - User user = userRepository.findById(userId) - .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + userUtils.findJwtUser(userId); + userUtils.checkDuplicateId(userId); + User user = userUtils.findIdUser(userId); GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { diff --git a/src/main/java/com/petmatz/domain/user/component/UserReader.java b/src/main/java/com/petmatz/domain/user/component/UserReader.java index 603f6fc..4fa24d5 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserReader.java +++ b/src/main/java/com/petmatz/domain/user/component/UserReader.java @@ -1,10 +1,14 @@ package com.petmatz.domain.user.component; import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.repository.UserRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import static com.petmatz.domain.user.exception.MatchErrorCode.USER_NOT_FOUND; + @Component @RequiredArgsConstructor public class UserReader { @@ -15,5 +19,4 @@ public User getAuthenticatedUser(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("User not found :" + userId)); } - } diff --git a/src/main/java/com/petmatz/domain/user/component/UserUtils.java b/src/main/java/com/petmatz/domain/user/component/UserUtils.java new file mode 100644 index 0000000..b0ae398 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/UserUtils.java @@ -0,0 +1,58 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.exception.UserException; +import com.petmatz.domain.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import static com.petmatz.domain.user.exception.MatchErrorCode.*; + +@Component +@RequiredArgsConstructor +public class UserUtils { + + private final UserRepository userRepository; + private final JwtExtractProvider jwtExtractProvider; + + public void checkDuplicateAccountId(String accountId) { + if (userRepository.existsByAccountId(accountId)) { + throw new UserException(USER_DUPLICATE); + } + } + + public void checkDuplicateId(Long userId) { + if (userRepository.existsById(userId)) { + throw new UserException(USER_DUPLICATE); + } + } + public User findUser(String accountId) { + User user = userRepository.findByAccountId(accountId); + if (user == null) { + throw new UserException(USER_NOT_FOUND); + } + return user; + } + + public User findIdUser(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + return user; + } + + + @Transactional + public User getCurrentUser(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new UserException(USER_NOT_FOUND)); + } + + public void findJwtUser(Long userId) { + if (userId == null) { + throw new UserException(JWT_USER_NOT_FOUND); + } + } + +} From d2c0a67887e06e6e6645f60b7c9065301e816ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Wed, 8 Jan 2025 23:00:30 +0900 Subject: [PATCH 08/13] =?UTF-8?q?Refactor=20:=20userUtils=20=EB=A1=9C=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=EC=9D=91=EB=8B=B5=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=AC=B6=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/component/LocationComponent.java | 42 ++-------------- .../user/component/LocationService.java | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/petmatz/domain/user/component/LocationService.java diff --git a/src/main/java/com/petmatz/domain/user/component/LocationComponent.java b/src/main/java/com/petmatz/domain/user/component/LocationComponent.java index 508e571..9988dcd 100644 --- a/src/main/java/com/petmatz/domain/user/component/LocationComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/LocationComponent.java @@ -1,54 +1,22 @@ package com.petmatz.domain.user.component; -import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.info.UpdateLocationInfo; import com.petmatz.domain.user.repository.UserRepository; import com.petmatz.domain.user.response.UpdateLocationResponseDto; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; + +import static com.petmatz.domain.user.exception.MatchErrorCode.USER_NOT_FOUND; @Component @RequiredArgsConstructor -@Slf4j public class LocationComponent { - private final JwtExtractProvider jwtExtractProvider; private final UserRepository userRepository; private final GeocodingService geocodingService; + private final UserUtils userUtils; - @Transactional - public ResponseEntity updateLocation(UpdateLocationInfo info) { - try { - // JWT에서 사용자 ID 추출 - Long userId = jwtExtractProvider.findIdFromJwt(); - - // 사용자 엔티티 조회 - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - // 사용자 존재 여부 확인 - boolean exists = userRepository.existsById(userId); - if (!exists) { - return UpdateLocationResponseDto.userNotFound(); - } - - // GeocodingService에서 지역명과 행정코드 가져오기 - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); - if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 - } - - user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - - // 성공 응답 반환 - return UpdateLocationResponseDto.success(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - } catch (Exception e) { - log.error("위치 업데이트 실패: {}", e.getMessage(), e); - return UpdateLocationResponseDto.wrongLocation(); - } - } + // 아마 지울듯? } diff --git a/src/main/java/com/petmatz/domain/user/component/LocationService.java b/src/main/java/com/petmatz/domain/user/component/LocationService.java new file mode 100644 index 0000000..fea66b9 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/component/LocationService.java @@ -0,0 +1,50 @@ +package com.petmatz.domain.user.component; + +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.UpdateLocationInfo; +import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.response.UpdateLocationResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class LocationService { + + private final JwtExtractProvider jwtExtractProvider; + private final UserRepository userRepository; + private final GeocodingService geocodingService; + + + @Transactional + public ResponseEntity updateLocation(UpdateLocationInfo info) { + try { + // JWT에서 사용자 ID 추출 + Long userId = jwtExtractProvider.findIdFromJwt(); + + // 사용자 엔티티 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + + + // GeocodingService에서 지역명과 행정코드 가져오기 + GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); + if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { + return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 + } + + user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); + + // 성공 응답 반환 + return UpdateLocationResponseDto.success(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); + } catch (Exception e) { + log.error("위치 업데이트 실패: {}", e.getMessage(), e); + return UpdateLocationResponseDto.wrongLocation(); + } + } +} From 2e5563fb652c382738aa0182c5e7970c7526bf1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Thu, 9 Jan 2025 00:42:01 +0900 Subject: [PATCH 09/13] =?UTF-8?q?Refactor=20:=20=EC=9C=84=EC=B9=98,=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC=ED=95=98=EA=B8=B0=20+=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EA=B3=B5=ED=86=B5util=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/component/GeocodingService.java | 3 - .../user/component/LocationComponent.java | 22 ------ .../user/component/LocationService.java | 30 ++----- .../domain/user/component/PageComponent.java | 79 +++++-------------- .../domain/user/component/UserUtils.java | 7 ++ .../domain/user/exception/MatchErrorCode.java | 3 +- .../response/GetMyProfileResponseDto.java | 1 - .../response/UpdateLocationResponseDto.java | 3 +- 8 files changed, 37 insertions(+), 111 deletions(-) delete mode 100644 src/main/java/com/petmatz/domain/user/component/LocationComponent.java diff --git a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java index 9cc748c..ea39aa4 100644 --- a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java +++ b/src/main/java/com/petmatz/domain/user/component/GeocodingService.java @@ -15,9 +15,6 @@ @Service public class GeocodingService { - - - @Value("${kakao-api-url}") private String KAKAO_API_URL; @Value("${kakao-api-key}") diff --git a/src/main/java/com/petmatz/domain/user/component/LocationComponent.java b/src/main/java/com/petmatz/domain/user/component/LocationComponent.java deleted file mode 100644 index 9988dcd..0000000 --- a/src/main/java/com/petmatz/domain/user/component/LocationComponent.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.petmatz.domain.user.component; - -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.exception.UserException; -import com.petmatz.domain.user.info.UpdateLocationInfo; -import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.UpdateLocationResponseDto; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import static com.petmatz.domain.user.exception.MatchErrorCode.USER_NOT_FOUND; - -@Component -@RequiredArgsConstructor -public class LocationComponent { - - private final UserRepository userRepository; - private final GeocodingService geocodingService; - private final UserUtils userUtils; - - // 아마 지울듯? -} diff --git a/src/main/java/com/petmatz/domain/user/component/LocationService.java b/src/main/java/com/petmatz/domain/user/component/LocationService.java index fea66b9..e7f6b21 100644 --- a/src/main/java/com/petmatz/domain/user/component/LocationService.java +++ b/src/main/java/com/petmatz/domain/user/component/LocationService.java @@ -17,34 +17,20 @@ public class LocationService { private final JwtExtractProvider jwtExtractProvider; - private final UserRepository userRepository; private final GeocodingService geocodingService; - + private final UserUtils userUtils; @Transactional - public ResponseEntity updateLocation(UpdateLocationInfo info) { - try { - // JWT에서 사용자 ID 추출 + public UpdateLocationResponseDto updateLocation(UpdateLocationInfo info) { Long userId = jwtExtractProvider.findIdFromJwt(); + User user = userUtils.findIdUser(userId); - // 사용자 엔티티 조회 - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - - // GeocodingService에서 지역명과 행정코드 가져오기 - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); - if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 - } - + /** + * 현재는 static 클래스라 이렇게 접근 + */ + GeocodingService.KakaoRegion kakaoRegion = geocodingService.getValidRegion(info.getLatitude(), info.getLongitude()); user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - // 성공 응답 반환 - return UpdateLocationResponseDto.success(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - } catch (Exception e) { - log.error("위치 업데이트 실패: {}", e.getMessage(), e); - return UpdateLocationResponseDto.wrongLocation(); - } + return new UpdateLocationResponseDto(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); } } diff --git a/src/main/java/com/petmatz/domain/user/component/PageComponent.java b/src/main/java/com/petmatz/domain/user/component/PageComponent.java index 110d0c9..87b842e 100644 --- a/src/main/java/com/petmatz/domain/user/component/PageComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/PageComponent.java @@ -16,6 +16,8 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.net.MalformedURLException; + @Component @RequiredArgsConstructor @Slf4j @@ -29,84 +31,41 @@ public class PageComponent { private final UserRepository userRepository; private final HeartRepository heartRepository; private final AwsClient awsClient; + private final UserUtils userUtils; - public ResponseEntity getMypage() { - try { - String userId = jwtExtractProvider.findAccountIdFromJwt(); - User user = userRepository.findByAccountId(userId); - - boolean exists = userRepository.existsByAccountId(userId); - if (!exists) { - return GetMyProfileResponseDto.idNotFound(); - } - - return GetMyProfileResponseDto.success(user); - - } catch (Exception e) { - e.printStackTrace(); - return GetMyProfileResponseDto.databaseError(); - } + public GetMyProfileResponseDto getMypage() { + String userId = jwtExtractProvider.findAccountIdFromJwt(); + User user = userUtils.findUser(userId); + return new GetMyProfileResponseDto(user); } - public ResponseEntity getOtherMypage(Long userId) { - try { - // 현재 로그인한 사용자 ID 가져오기 - Long myId = jwtExtractProvider.findIdFromJwt(); - if (!userRepository.existsById(myId)) { - return GetMyProfileResponseDto.idNotFound(); - } + public GetOtherProfileResponseDto getOtherMypage(Long userId) { + Long myId = jwtExtractProvider.findIdFromJwt(); + User user = userUtils.findIdUser(myId); - // 조회 대상 사용자 정보 가져오기 - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + // 조회 대상 사용자가 존재하는지 확인 + userUtils.findIdUser(userId); - // 조회 대상 사용자가 존재하는지 확인 - boolean exists = userRepository.existsById(userId); - if (!exists) { - return GetOtherProfileResponseDto.userNotFound(); - } + // 현재 로그인한 사용자가 조회 대상 사용자를 찜했는지 확인 + boolean checkHeart = userUtils.checkHeart(myId, userId); - // 현재 로그인한 사용자가 조회 대상 사용자를 찜했는지 확인 - boolean isMyHeartUser = heartRepository.existsByMyIdAndHeartedId(myId, userId); - - // 응답 생성 - return GetOtherProfileResponseDto.success(user, isMyHeartUser); - - } catch (Exception e) { - e.printStackTrace(); - return GetOtherProfileResponseDto.userNotFound(); - } + return new GetOtherProfileResponseDto(user, checkHeart); } - //TODO 고쳐야함. ...? @Transactional - public ResponseEntity editMyProfile(EditMyProfileInfo info) { - try { - System.out.println("info ::" + info.isCareAvailable()); + public EditMyProfileResponseDto editMyProfile(EditMyProfileInfo info) throws MalformedURLException { Long userId = jwtExtractProvider.findIdFromJwt(); String userEmail = jwtExtractProvider.findAccountIdFromJwt(); - boolean exists = userRepository.existsById(userId); - if (!exists) { - return EditMyProfileResponseDto.idNotFound(); - } + User user = userUtils.findIdUser(userId); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - //6-1 Img 정제 + //Img 정제 S3Imge petImg = awsClient.UploadImg(userEmail, info.getProfileImg(), "CUSTOM_USER_IMG", null); // 병합된 DTO를 기반으로 엔티티 생성 String resultImg = user.updateImgURL(info.getProfileImg(), petImg); user.updateProfile(info); -// 반환해야 함 아래꺼 - return EditMyProfileResponseDto.success(resultImg); - - } catch (Exception e) { - log.info("프로필 수정 실패: {}", e); - return EditMyProfileResponseDto.editFailed(); - } + return new EditMyProfileResponseDto(resultImg); } } diff --git a/src/main/java/com/petmatz/domain/user/component/UserUtils.java b/src/main/java/com/petmatz/domain/user/component/UserUtils.java index b0ae398..388e15e 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserUtils.java +++ b/src/main/java/com/petmatz/domain/user/component/UserUtils.java @@ -3,6 +3,7 @@ import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.exception.UserException; +import com.petmatz.domain.user.repository.HeartRepository; import com.petmatz.domain.user.repository.UserRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -16,6 +17,7 @@ public class UserUtils { private final UserRepository userRepository; private final JwtExtractProvider jwtExtractProvider; + private final HeartRepository heartRepository; public void checkDuplicateAccountId(String accountId) { if (userRepository.existsByAccountId(accountId)) { @@ -55,4 +57,9 @@ public void findJwtUser(Long userId) { } } + public boolean checkHeart(Long myId, Long userId) { + if (heartRepository.existsByMyIdAndHeartedId(myId, userId)) { + throw new UserException(HEART_USER_NOT_FOUND); + } + } } diff --git a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java index 78e5b94..e9f1322 100644 --- a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java +++ b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java @@ -17,7 +17,8 @@ public enum MatchErrorCode implements BaseErrorCode { CERTIFICATION_EXPIRED(400, "CERTIFICATION_EXPIRED", "인증 시간이 만료되었습니다."), MISS_MATCH_CODE(400, "MISS_MATCH_CODE", "인증 번호가 일치하지 않습니다."), USER_DUPLICATE(400, "USER_DUPLICATE", "중복된 사용자가 있습니다."), - HEART_USER_NOT_FOUND(404, "HEART_USER_DUPLICATE", "찜한 사용자를 찾을 수 없습니다."); + HEART_USER_NOT_FOUND(404, "HEART_USER_NOT_FOUND", "찜한 사용자를 찾을 수 없습니다."), + HEART_USER_DUPLICATE(400, "HEART_USER_DUPLICATE", "찜한 사용자가 이미 존재합니다."); diff --git a/src/main/java/com/petmatz/domain/user/response/GetMyProfileResponseDto.java b/src/main/java/com/petmatz/domain/user/response/GetMyProfileResponseDto.java index fcdee72..babccbe 100644 --- a/src/main/java/com/petmatz/domain/user/response/GetMyProfileResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/GetMyProfileResponseDto.java @@ -30,7 +30,6 @@ public class GetMyProfileResponseDto extends LogInResponseDto { private Integer regionCode; public GetMyProfileResponseDto(User user) { - super(); this.id = user.getId(); this.accountId = user.getAccountId(); this.nickname = user.getNickname(); diff --git a/src/main/java/com/petmatz/domain/user/response/UpdateLocationResponseDto.java b/src/main/java/com/petmatz/domain/user/response/UpdateLocationResponseDto.java index d247f36..d614f57 100644 --- a/src/main/java/com/petmatz/domain/user/response/UpdateLocationResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/UpdateLocationResponseDto.java @@ -12,8 +12,7 @@ public class UpdateLocationResponseDto extends LogInResponseDto{ private String region; private Integer regionCode; - private UpdateLocationResponseDto(String region,Integer regionCode){ - super(); + public UpdateLocationResponseDto(String region, Integer regionCode){ this.region = region; this.regionCode=regionCode; } From 3b704b5384f0d39dc1b45d6b658e972b355865f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Fri, 10 Jan 2025 01:11:08 +0900 Subject: [PATCH 10/13] =?UTF-8?q?Refactor=20:=201=EC=B0=A8=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20|=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B6=84=EB=A6=AC=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=99=84=EB=A3=8C!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/user/controller/UserController.java | 2 +- ...ngService.java => GeocodingComponent.java} | 51 +- .../user/component/KakaoUserComponent.java | 8 +- .../user/component/RecommendComponent.java | 58 +- .../domain/user/component/UserComponent.java | 52 +- .../domain/user/component/UserReader.java | 3 + .../domain/user/component/UserUtils.java | 4 +- .../domain/user/entity/KakaoRegion.java | 37 + .../domain/user/exception/MatchErrorCode.java | 2 + .../GetRecommendationResponseDto.java | 3 +- .../user/response/KakaoGeocodingResponse.java | 11 + .../response/SendRepasswordResponseDto.java | 3 +- .../{component => service}/AuthService.java | 13 +- .../CustomOAuth2UserService.java | 3 +- .../domain/user/service/EmailProvider.java | 2 +- .../{component => service}/EmailService.java | 4 +- .../{component => service}/HeartService.java | 4 +- .../LocationService.java | 11 +- .../domain/user/service/PasswordService.java | 58 + .../user/service/RePasswordEmailProvider.java | 2 +- .../domain/user/service/RecommendService.java | 48 + .../domain/user/service/UserServiceImpl.java | 1244 ++++++++--------- .../infra/email/EmailProviderImpl.java | 11 +- .../email/RePasswordEmailProviderImpl.java | 11 +- 24 files changed, 855 insertions(+), 790 deletions(-) rename src/main/java/com/petmatz/domain/user/component/{GeocodingService.java => GeocodingComponent.java} (60%) create mode 100644 src/main/java/com/petmatz/domain/user/entity/KakaoRegion.java create mode 100644 src/main/java/com/petmatz/domain/user/response/KakaoGeocodingResponse.java rename src/main/java/com/petmatz/domain/user/{component => service}/AuthService.java (91%) rename src/main/java/com/petmatz/domain/user/{component => service}/CustomOAuth2UserService.java (90%) rename src/main/java/com/petmatz/domain/user/{component => service}/EmailService.java (89%) rename src/main/java/com/petmatz/domain/user/{component => service}/HeartService.java (90%) rename src/main/java/com/petmatz/domain/user/{component => service}/LocationService.java (74%) create mode 100644 src/main/java/com/petmatz/domain/user/service/PasswordService.java create mode 100644 src/main/java/com/petmatz/domain/user/service/RecommendService.java diff --git a/src/main/java/com/petmatz/api/user/controller/UserController.java b/src/main/java/com/petmatz/api/user/controller/UserController.java index ef8946f..046f1b0 100644 --- a/src/main/java/com/petmatz/api/user/controller/UserController.java +++ b/src/main/java/com/petmatz/api/user/controller/UserController.java @@ -1,7 +1,7 @@ package com.petmatz.api.user.controller; import com.petmatz.api.user.request.*; -import com.petmatz.domain.user.component.AuthService; +import com.petmatz.domain.user.service.AuthService; import com.petmatz.domain.user.response.*; import com.petmatz.domain.user.service.UserService; import com.petmatz.user.common.LogInResponseDto; diff --git a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java b/src/main/java/com/petmatz/domain/user/component/GeocodingComponent.java similarity index 60% rename from src/main/java/com/petmatz/domain/user/component/GeocodingService.java rename to src/main/java/com/petmatz/domain/user/component/GeocodingComponent.java index ea39aa4..6fc3304 100644 --- a/src/main/java/com/petmatz/domain/user/component/GeocodingService.java +++ b/src/main/java/com/petmatz/domain/user/component/GeocodingComponent.java @@ -1,22 +1,25 @@ package com.petmatz.domain.user.component; import com.fasterxml.jackson.annotation.JsonProperty; -import com.petmatz.domain.match.exception.MatchException; +import com.petmatz.domain.user.entity.KakaoRegion; import com.petmatz.domain.user.exception.UserException; +import com.petmatz.domain.user.response.KakaoGeocodingResponse; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import com.petmatz.domain.user.entity.KakaoRegion; + import java.util.List; import static com.petmatz.domain.user.exception.MatchErrorCode.INSUFFICIENT_LOCATION_DATA; @Service -public class GeocodingService { +public class GeocodingComponent { @Value("${kakao-api-url}") - private String KAKAO_API_URL; + private String KAKAO_API_URL; @Value("${kakao-api-key}") private String KAKAO_API_KEY; @@ -63,50 +66,12 @@ private void logInfo(KakaoRegion region) { /** * 좌표를 기반으로 지역 정보를 가져오고 유효성을 검증 */ - public GeocodingService.KakaoRegion getValidRegion(double latitude, double longitude) { - GeocodingService.KakaoRegion kakaoRegion = getRegionFromCoordinates(latitude, longitude); + public KakaoRegion getValidRegion(double latitude, double longitude) { + KakaoRegion kakaoRegion = getRegionFromCoordinates(latitude, longitude); // 지역 정보 검증 if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { throw new UserException(INSUFFICIENT_LOCATION_DATA); } return kakaoRegion; } - - @Data - static class KakaoGeocodingResponse { - private List documents; - } - - @Data - static class KakaoRegion { - @JsonProperty("region_1depth_name") - private String region1; // 예: 서울 - @JsonProperty("region_2depth_name") - private String region2; // 예: 강남구 역삼동 - @JsonProperty("region_3depth_name") - private String region3; // 예: 역삼동 - @JsonProperty("code") - private String code; // 행정구역 코드 (String 타입) - - /** - * '구'까지만 반환하는 메서드 - * - * @return "서울 강남구"와 같은 형식 - */ - public String getRegionName() { - // '구'까지만 표시되도록 region2를 공백 기준으로 split 후 첫 번째 단어 반환 - String region2Trimmed = region2.contains(" ") ? region2.split(" ")[0] : region2; - return region1 + " " + region2Trimmed; - } - - public Integer getCodeAsInteger() { - try { - String trimmedCode = code.length() > 6 ? code.substring(0, 6) : code; - return Integer.parseInt(trimmedCode); - } catch (NumberFormatException e) { - System.err.println("Failed to parse region code: " + code); - return null; - } - } - } } \ No newline at end of file diff --git a/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java b/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java index 52cd98b..7107762 100644 --- a/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java @@ -3,15 +3,13 @@ import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.user.constant.LoginRole; import com.petmatz.domain.user.constant.LoginType; +import com.petmatz.domain.user.entity.KakaoRegion; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.info.EditKakaoProfileInfo; import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.EditKakaoProfileResponseDto; -import com.petmatz.domain.user.response.UpdateLocationResponseDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -28,7 +26,7 @@ public class KakaoUserComponent { private final JwtExtractProvider jwtExtractProvider; private final UserRepository userRepository; - private final GeocodingService geocodingService; + private final GeocodingComponent geocodingComponent; private final UserUtils userUtils; @Transactional @@ -38,7 +36,7 @@ public void editKakaoProfile(EditKakaoProfileInfo info) { userUtils.checkDuplicateId(userId); User user = userUtils.findIdUser(userId); - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); + KakaoRegion kakaoRegion = geocodingComponent.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { throw new UserException(MISS_KAKAO_LOACTION); } diff --git a/src/main/java/com/petmatz/domain/user/component/RecommendComponent.java b/src/main/java/com/petmatz/domain/user/component/RecommendComponent.java index 5ad3f25..d72f451 100644 --- a/src/main/java/com/petmatz/domain/user/component/RecommendComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/RecommendComponent.java @@ -1,68 +1,20 @@ package com.petmatz.domain.user.component; -import com.petmatz.api.user.request.UpdateRecommendationRequestDto; -import com.petmatz.common.security.utils.JwtExtractProvider; -import com.petmatz.domain.user.entity.Recommendation; -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.entity.UserFactory; import com.petmatz.domain.user.repository.RecommendationRepository; -import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.GetRecommendationResponseDto; -import com.petmatz.domain.user.response.UpdateRecommendationResponseDto; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; @Component @RequiredArgsConstructor -@Slf4j public class RecommendComponent { - private final JwtExtractProvider jwtExtractProvider; - private final UserRepository userRepository; private final RecommendationRepository recommendationRepository; - - @Transactional - public ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto) { - try { - Long recommendedId = dto.getUserId(); - Long myId = jwtExtractProvider.findIdFromJwt(); - User recommendedUser = userRepository.findById(recommendedId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + recommendedId)); - - boolean exists = userRepository.existsById(recommendedId); - if (!exists) { - return UpdateRecommendationResponseDto.userNotFound(); - } - Integer recommendationCount = recommendedUser.getRecommendationCount() + 1; - - recommendedUser.updateRecommendation(recommendationCount); - - Recommendation recommendation = UserFactory.createRecommendation(myId, recommendedId); - recommendationRepository.save(recommendation); - - } catch (Exception e) { - log.info("추천수 업데이트 실패: {}", e); - return UpdateRecommendationResponseDto.userNotFound(); + public boolean validateRecommendUser(Long myId, Long recommendUserId) { + boolean exists = recommendationRepository.existsByMyIdAndRecommendedId(myId, recommendUserId); + if (exists) { + return true; } - return UpdateRecommendationResponseDto.success(); + return false; } - - public ResponseEntity getRecommend(UpdateRecommendationRequestDto dto) { - try { - Long recommendedId = dto.getUserId(); - Long myId = jwtExtractProvider.findIdFromJwt(); - - boolean exists = recommendationRepository.existsByMyIdAndRecommendedId(myId,recommendedId); - return GetRecommendationResponseDto.success(exists); - - } catch (Exception e) { - log.info("추천수 업데이트 실패: {}", e); - return GetRecommendationResponseDto.userNotFound(); - } - } - } diff --git a/src/main/java/com/petmatz/domain/user/component/UserComponent.java b/src/main/java/com/petmatz/domain/user/component/UserComponent.java index 56d0f2c..ff5be8f 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/UserComponent.java @@ -35,46 +35,32 @@ public class UserComponent { private final UserRepository userRepository; private final CertificationRepository certificationRepository; private final JwtExtractProvider jwtExtractProvider; - private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); private final PetRepository petRepository; + private final UserUtils userUtils; + private final PasswordComponent passwordComponent; @Transactional - public ResponseEntity deleteId(DeleteIdRequestDto dto) { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - boolean exists = userRepository.existsById(userId); - if (!exists) { - return DeleteIdResponseDto.idNotFound(); - } + public void deleteId(DeleteIdRequestDto dto) { + Long userId = jwtExtractProvider.findIdFromJwt(); + User user = userUtils.findIdUser(userId); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); + String password = dto.getPassword(); + String encodedPassword = user.getPassword(); - - // 비밀번호 일치 확인 - String password = dto.getPassword(); - String encodedPassword = user.getPassword(); - boolean isMatched = passwordEncoder.matches(password, encodedPassword); - if (!isMatched) return DeleteIdResponseDto.wrongPassword(); // 비밀번호 불일치 - - certificationRepository.deleteById(userId); - // 사용자 삭제 - List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 - // 명시적으로 Pet 삭제 - petRepository.deleteAll(pets); - userRepository.delete(user); - } catch (Exception e) { - log.info("회원 삭제 실패: {}", e); - return DeleteIdResponseDto.databaseError(); // 데이터베이스 오류 처리 - } - return DeleteIdResponseDto.success(); // 삭제 성공 응답 + passwordComponent.validatePassword(password, encodedPassword); + certificationRepository.deleteById(userId); + // 사용자 삭제 + List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 + // 명시적으로 Pet 삭제 + petRepository.deleteAll(pets); + userRepository.delete(user); + /** + * 여기 종원님이랑 머지하고 펫레포에서 지우는거 만들어달라고 의뢰 예정 + */ } - public void deleteUser(Long userUUID) { - userRepository.deleteUserById(userUUID); - } public UserInfo selectUserInfo(String receiverEmail) { User otherUser = userRepository.findByAccountId(receiverEmail); @@ -84,4 +70,8 @@ public UserInfo selectUserInfo(String receiverEmail) { public String findByUserEmail(Long userId) { return userRepository.findById(userId).get().getAccountId(); } + + public void deleteUser(Long userUUID) { + userRepository.deleteUserById(userUUID); + } } diff --git a/src/main/java/com/petmatz/domain/user/component/UserReader.java b/src/main/java/com/petmatz/domain/user/component/UserReader.java index 4fa24d5..8e54c8d 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserReader.java +++ b/src/main/java/com/petmatz/domain/user/component/UserReader.java @@ -13,6 +13,9 @@ @RequiredArgsConstructor public class UserReader { + /** + * 이것도 utils를 만들어서 곧 지울듯 + */ private final UserRepository userRepository; public User getAuthenticatedUser(Long userId) { diff --git a/src/main/java/com/petmatz/domain/user/component/UserUtils.java b/src/main/java/com/petmatz/domain/user/component/UserUtils.java index 388e15e..42df080 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserUtils.java +++ b/src/main/java/com/petmatz/domain/user/component/UserUtils.java @@ -16,7 +16,6 @@ public class UserUtils { private final UserRepository userRepository; - private final JwtExtractProvider jwtExtractProvider; private final HeartRepository heartRepository; public void checkDuplicateAccountId(String accountId) { @@ -59,7 +58,8 @@ public void findJwtUser(Long userId) { public boolean checkHeart(Long myId, Long userId) { if (heartRepository.existsByMyIdAndHeartedId(myId, userId)) { - throw new UserException(HEART_USER_NOT_FOUND); + return false; } + return true; } } diff --git a/src/main/java/com/petmatz/domain/user/entity/KakaoRegion.java b/src/main/java/com/petmatz/domain/user/entity/KakaoRegion.java new file mode 100644 index 0000000..4603b33 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/entity/KakaoRegion.java @@ -0,0 +1,37 @@ +package com.petmatz.domain.user.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class KakaoRegion { + @JsonProperty("region_1depth_name") + private String region1; // 예: 서울 + @JsonProperty("region_2depth_name") + private String region2; // 예: 강남구 역삼동 + @JsonProperty("region_3depth_name") + private String region3; // 예: 역삼동 + @JsonProperty("code") + private String code; // 행정구역 코드 (String 타입) + + /** + * '구'까지만 반환하는 메서드 + * + * @return "서울 강남구"와 같은 형식 + */ + public String getRegionName() { + // '구'까지만 표시되도록 region2를 공백 기준으로 split 후 첫 번째 단어 반환 + String region2Trimmed = region2.contains(" ") ? region2.split(" ")[0] : region2; + return region1 + " " + region2Trimmed; + } + + public Integer getCodeAsInteger() { + try { + String trimmedCode = code.length() > 6 ? code.substring(0, 6) : code; + return Integer.parseInt(trimmedCode); + } catch (NumberFormatException e) { + System.err.println("Failed to parse region code: " + code); + return null; + } + } +} diff --git a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java index e9f1322..c0f7184 100644 --- a/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java +++ b/src/main/java/com/petmatz/domain/user/exception/MatchErrorCode.java @@ -18,6 +18,8 @@ public enum MatchErrorCode implements BaseErrorCode { MISS_MATCH_CODE(400, "MISS_MATCH_CODE", "인증 번호가 일치하지 않습니다."), USER_DUPLICATE(400, "USER_DUPLICATE", "중복된 사용자가 있습니다."), HEART_USER_NOT_FOUND(404, "HEART_USER_NOT_FOUND", "찜한 사용자를 찾을 수 없습니다."), + FAIL_MAIL_SEND(400, "FAIL_MAIL_SEND", "메일 전송에 실패하였습니다."), + PASSWORD_MISMATCH(403, "PASSWORD_MISMATCH", "비밀번호가 일치하지 않습니다."), HEART_USER_DUPLICATE(400, "HEART_USER_DUPLICATE", "찜한 사용자가 이미 존재합니다."); diff --git a/src/main/java/com/petmatz/domain/user/response/GetRecommendationResponseDto.java b/src/main/java/com/petmatz/domain/user/response/GetRecommendationResponseDto.java index 92f432a..9ed9899 100644 --- a/src/main/java/com/petmatz/domain/user/response/GetRecommendationResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/GetRecommendationResponseDto.java @@ -11,8 +11,7 @@ public class GetRecommendationResponseDto extends LogInResponseDto { private boolean isRecommended; - private GetRecommendationResponseDto(boolean isRecommended) { - super(); + public GetRecommendationResponseDto(boolean isRecommended) { this.isRecommended=isRecommended; } diff --git a/src/main/java/com/petmatz/domain/user/response/KakaoGeocodingResponse.java b/src/main/java/com/petmatz/domain/user/response/KakaoGeocodingResponse.java new file mode 100644 index 0000000..0f28913 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/response/KakaoGeocodingResponse.java @@ -0,0 +1,11 @@ +package com.petmatz.domain.user.response; + +import com.petmatz.domain.user.entity.KakaoRegion; +import lombok.Data; + +import java.util.List; + +@Data +public class KakaoGeocodingResponse { + private List documents; +} diff --git a/src/main/java/com/petmatz/domain/user/response/SendRepasswordResponseDto.java b/src/main/java/com/petmatz/domain/user/response/SendRepasswordResponseDto.java index a8b8eeb..55d9208 100644 --- a/src/main/java/com/petmatz/domain/user/response/SendRepasswordResponseDto.java +++ b/src/main/java/com/petmatz/domain/user/response/SendRepasswordResponseDto.java @@ -10,8 +10,7 @@ @Getter public class SendRepasswordResponseDto extends LogInResponseDto{ - private SendRepasswordResponseDto(){ - super(); + public SendRepasswordResponseDto(){ } public static ResponseEntity mailSendFail(){ diff --git a/src/main/java/com/petmatz/domain/user/component/AuthService.java b/src/main/java/com/petmatz/domain/user/service/AuthService.java similarity index 91% rename from src/main/java/com/petmatz/domain/user/component/AuthService.java rename to src/main/java/com/petmatz/domain/user/service/AuthService.java index 1293721..885a070 100644 --- a/src/main/java/com/petmatz/domain/user/component/AuthService.java +++ b/src/main/java/com/petmatz/domain/user/service/AuthService.java @@ -1,9 +1,11 @@ -package com.petmatz.domain.user.component; +package com.petmatz.domain.user.service; import com.petmatz.common.security.utils.JwtProvider; import com.petmatz.domain.aws.AwsClient; import com.petmatz.domain.aws.vo.S3Imge; +import com.petmatz.domain.user.component.AuthenticationComponent; import com.petmatz.domain.user.entity.Certification; +import com.petmatz.domain.user.entity.KakaoRegion; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.entity.UserFactory; import com.petmatz.domain.user.info.CheckCertificationInfo; @@ -11,15 +13,13 @@ import com.petmatz.domain.user.info.SignUpInfo; import com.petmatz.domain.user.repository.CertificationRepository; import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.CheckCertificationResponseDto; import com.petmatz.domain.user.response.SignInResponseDto; import com.petmatz.domain.user.response.SignUpResponseDto; -import com.petmatz.user.common.LogInResponseDto; +import com.petmatz.domain.user.component.GeocodingComponent; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -28,7 +28,6 @@ import javax.naming.AuthenticationException; import java.net.MalformedURLException; import java.security.cert.CertificateException; -import java.time.LocalDateTime; @Component @RequiredArgsConstructor @@ -42,7 +41,7 @@ public class AuthService { private final UserRepository userRepository; private final CertificationRepository certificationRepository; - private final GeocodingService geocodingService; + private final GeocodingComponent geocodingComponent; private final AwsClient awsClient; // 추후에 수정 private final JwtProvider jwtProvider; private final AuthenticationComponent authenticationComponent; @@ -62,7 +61,7 @@ public SignUpResponseDto signUp(SignUpInfo info) throws MalformedURLException { String encodedPassword = passwordEncoder.encode(info.getPassword()); // 지역명과 6자리 행정코드 가져오기 - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getValidRegion(info.getLatitude(), info.getLongitude()); + KakaoRegion kakaoRegion = geocodingComponent.getValidRegion(info.getLatitude(), info.getLongitude()); //6-1 Img 정제 S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); diff --git a/src/main/java/com/petmatz/domain/user/component/CustomOAuth2UserService.java b/src/main/java/com/petmatz/domain/user/service/CustomOAuth2UserService.java similarity index 90% rename from src/main/java/com/petmatz/domain/user/component/CustomOAuth2UserService.java rename to src/main/java/com/petmatz/domain/user/service/CustomOAuth2UserService.java index f43c1c4..dd2e39b 100644 --- a/src/main/java/com/petmatz/domain/user/component/CustomOAuth2UserService.java +++ b/src/main/java/com/petmatz/domain/user/service/CustomOAuth2UserService.java @@ -1,5 +1,6 @@ -package com.petmatz.domain.user.component; +package com.petmatz.domain.user.service; +import com.petmatz.domain.user.component.OAuth2UserLoader; import lombok.RequiredArgsConstructor; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; diff --git a/src/main/java/com/petmatz/domain/user/service/EmailProvider.java b/src/main/java/com/petmatz/domain/user/service/EmailProvider.java index a67349b..9e2d33c 100644 --- a/src/main/java/com/petmatz/domain/user/service/EmailProvider.java +++ b/src/main/java/com/petmatz/domain/user/service/EmailProvider.java @@ -1,5 +1,5 @@ package com.petmatz.domain.user.service; public interface EmailProvider { - boolean sendVerificationEmail(String email, String certificationNumber); + void sendVerificationEmail(String email, String certificationNumber); } diff --git a/src/main/java/com/petmatz/domain/user/component/EmailService.java b/src/main/java/com/petmatz/domain/user/service/EmailService.java similarity index 89% rename from src/main/java/com/petmatz/domain/user/component/EmailService.java rename to src/main/java/com/petmatz/domain/user/service/EmailService.java index 150de9f..fb11e4a 100644 --- a/src/main/java/com/petmatz/domain/user/component/EmailService.java +++ b/src/main/java/com/petmatz/domain/user/service/EmailService.java @@ -1,6 +1,8 @@ -package com.petmatz.domain.user.component; +package com.petmatz.domain.user.service; import com.petmatz.api.user.request.EmailCertificationRequestDto; +import com.petmatz.domain.user.component.EmailComponent; +import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.provider.CertificationNumberProvider; import com.petmatz.domain.user.repository.UserRepository; diff --git a/src/main/java/com/petmatz/domain/user/component/HeartService.java b/src/main/java/com/petmatz/domain/user/service/HeartService.java similarity index 90% rename from src/main/java/com/petmatz/domain/user/component/HeartService.java rename to src/main/java/com/petmatz/domain/user/service/HeartService.java index 9ef4504..fecbfdb 100644 --- a/src/main/java/com/petmatz/domain/user/component/HeartService.java +++ b/src/main/java/com/petmatz/domain/user/service/HeartService.java @@ -1,8 +1,10 @@ -package com.petmatz.domain.user.component; +package com.petmatz.domain.user.service; import com.petmatz.api.user.request.HeartedUserDto; import com.petmatz.api.user.request.HeartingRequestDto; import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.component.HeartComponent; +import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.repository.HeartRepository; import com.petmatz.domain.user.repository.UserRepository; diff --git a/src/main/java/com/petmatz/domain/user/component/LocationService.java b/src/main/java/com/petmatz/domain/user/service/LocationService.java similarity index 74% rename from src/main/java/com/petmatz/domain/user/component/LocationService.java rename to src/main/java/com/petmatz/domain/user/service/LocationService.java index e7f6b21..82649b1 100644 --- a/src/main/java/com/petmatz/domain/user/component/LocationService.java +++ b/src/main/java/com/petmatz/domain/user/service/LocationService.java @@ -1,13 +1,14 @@ -package com.petmatz.domain.user.component; +package com.petmatz.domain.user.service; import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.component.UserUtils; +import com.petmatz.domain.user.entity.KakaoRegion; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.info.UpdateLocationInfo; -import com.petmatz.domain.user.repository.UserRepository; import com.petmatz.domain.user.response.UpdateLocationResponseDto; +import com.petmatz.domain.user.component.GeocodingComponent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -17,7 +18,7 @@ public class LocationService { private final JwtExtractProvider jwtExtractProvider; - private final GeocodingService geocodingService; + private final GeocodingComponent geocodingComponent; private final UserUtils userUtils; @Transactional @@ -28,7 +29,7 @@ public UpdateLocationResponseDto updateLocation(UpdateLocationInfo info) { /** * 현재는 static 클래스라 이렇게 접근 */ - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getValidRegion(info.getLatitude(), info.getLongitude()); + KakaoRegion kakaoRegion = geocodingComponent.getValidRegion(info.getLatitude(), info.getLongitude()); user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); return new UpdateLocationResponseDto(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); diff --git a/src/main/java/com/petmatz/domain/user/service/PasswordService.java b/src/main/java/com/petmatz/domain/user/service/PasswordService.java new file mode 100644 index 0000000..87b3c35 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/service/PasswordService.java @@ -0,0 +1,58 @@ +package com.petmatz.domain.user.service; + +import com.petmatz.api.user.request.SendRepasswordRequestDto; +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.component.PasswordComponent; +import com.petmatz.domain.user.component.UserUtils; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.info.RepasswordInfo; +import com.petmatz.domain.user.provider.RePasswordProvider; +import com.petmatz.domain.user.response.RepasswordResponseDto; +import com.petmatz.domain.user.response.SendRepasswordResponseDto; +import com.petmatz.domain.user.service.RePasswordEmailProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PasswordService { + + private final RePasswordEmailProvider rePasswordEmailProvider; + private final JwtExtractProvider jwtExtractProvider; + private final UserUtils userUtils; + private final PasswordComponent passwordComponent; + + @Transactional + public SendRepasswordResponseDto sendRepassword(SendRepasswordRequestDto dto) { + String accountId = dto.getAccountId(); + User user = userUtils.findUser(accountId); + + String rePasswordNum = RePasswordProvider.generatePassword(); + + rePasswordEmailProvider.sendVerificationEmail(accountId, rePasswordNum); + String encodedRePasswordNum = passwordComponent.encodePassword(rePasswordNum); + user.updatePassword(encodedRePasswordNum); + + return new SendRepasswordResponseDto(); + } + + @Transactional + public void repassword(RepasswordInfo info) { + Long userId = jwtExtractProvider.findIdFromJwt(); + User user = userUtils.findIdUser(userId); + + String currentPassword = info.getCurrentPassword(); + passwordComponent.validatePassword(currentPassword, user.getPassword()); + + String newPassword = info.getNewPassword(); + + String encodedNewPassword = passwordComponent.encodePassword(newPassword); + user.updatePassword(encodedNewPassword); + } +} diff --git a/src/main/java/com/petmatz/domain/user/service/RePasswordEmailProvider.java b/src/main/java/com/petmatz/domain/user/service/RePasswordEmailProvider.java index 7bc19e8..c91d01a 100644 --- a/src/main/java/com/petmatz/domain/user/service/RePasswordEmailProvider.java +++ b/src/main/java/com/petmatz/domain/user/service/RePasswordEmailProvider.java @@ -1,5 +1,5 @@ package com.petmatz.domain.user.service; public interface RePasswordEmailProvider { - boolean sendVerificationEmail(String email, String rePassword); + void sendVerificationEmail(String email, String rePassword); } diff --git a/src/main/java/com/petmatz/domain/user/service/RecommendService.java b/src/main/java/com/petmatz/domain/user/service/RecommendService.java new file mode 100644 index 0000000..8e75cf6 --- /dev/null +++ b/src/main/java/com/petmatz/domain/user/service/RecommendService.java @@ -0,0 +1,48 @@ +package com.petmatz.domain.user.service; + +import com.petmatz.api.user.request.UpdateRecommendationRequestDto; +import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.component.RecommendComponent; +import com.petmatz.domain.user.component.UserUtils; +import com.petmatz.domain.user.entity.Recommendation; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.entity.UserFactory; +import com.petmatz.domain.user.repository.RecommendationRepository; +import com.petmatz.domain.user.response.GetRecommendationResponseDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Slf4j +public class RecommendService { + + private final JwtExtractProvider jwtExtractProvider; + private final RecommendationRepository recommendationRepository; + private final UserUtils userUtils; + private final RecommendComponent recommendComponent; + + @Transactional + public void updateRecommend(UpdateRecommendationRequestDto dto) { + Long recommendedId = dto.getUserId(); + Long myId = jwtExtractProvider.findIdFromJwt(); + User recommendedUser = userUtils.findIdUser(recommendedId); + + Integer recommendationCount = recommendedUser.getRecommendationCount() + 1; + + recommendedUser.updateRecommendation(recommendationCount); + + Recommendation recommendation = UserFactory.createRecommendation(myId, recommendedId); + recommendationRepository.save(recommendation); + } + + public GetRecommendationResponseDto getRecommend(UpdateRecommendationRequestDto dto) { + Long recommendedId = dto.getUserId(); + Long myId = jwtExtractProvider.findIdFromJwt(); + + boolean recommendUser = recommendComponent.validateRecommendUser(myId, recommendedId); + return new GetRecommendationResponseDto(recommendUser); + } +} diff --git a/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java b/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java index 51b5393..dc09236 100644 --- a/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/petmatz/domain/user/service/UserServiceImpl.java @@ -1,622 +1,622 @@ -package com.petmatz.domain.user.service; - -import com.petmatz.api.user.request.*; -import com.petmatz.common.security.utils.JwtExtractProvider; -import com.petmatz.common.security.utils.JwtProvider; -import com.petmatz.domain.aws.AwsClient; -import com.petmatz.domain.aws.vo.S3Imge; -import com.petmatz.domain.pet.entity.Pet; -import com.petmatz.domain.pet.repository.PetRepository; -import com.petmatz.domain.user.component.GeocodingService; -import com.petmatz.domain.user.entity.*; -import com.petmatz.domain.user.info.*; -import com.petmatz.domain.user.provider.CertificationNumberProvider; -import com.petmatz.domain.user.provider.RePasswordProvider; -import com.petmatz.domain.user.repository.CertificationRepository; -import com.petmatz.domain.user.repository.HeartRepository; -import com.petmatz.domain.user.repository.RecommendationRepository; -import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.*; -import com.petmatz.user.common.LogInResponseDto; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -@Service -@Slf4j -@RequiredArgsConstructor -public class UserServiceImpl implements UserService { - - private final UserRepository userRepository; - private final CertificationRepository certificationRepository; - private final HeartRepository heartRepository; - private final RecommendationRepository recommendationRepository; - private final JwtProvider jwtProvider; - private final EmailProvider emailProvider; - private final RePasswordEmailProvider rePasswordEmailProvider; - private final PetRepository petRepository; - - private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - private final JwtExtractProvider jwtExtractProvider; - - private final GeocodingService geocodingService; - - private final AwsClient awsClient; - - - @Override - public ResponseEntity emailCertification(EmailCertificationRequestDto dto) { - try { - String accountId = dto.getAccountId(); - - //이메일 전송과 동시에 아이디 중복검사 - boolean isExistId = userRepository.existsByAccountId(accountId); - if (isExistId) return EmailCertificationResponseDto.duplicateId(); - - // 인증 번호 생성 및 이메일 전송 - String certificationNumber = CertificationNumberProvider.generateNumber(); - boolean isSendSuccess = emailProvider.sendVerificationEmail(accountId, certificationNumber); - if (!isSendSuccess) return EmailCertificationResponseDto.mailSendFail(); - - // 인증 엔티티 저장 - Certification certification = Certification.builder().accountId(accountId).certificationNumber(certificationNumber).isVerified(false).build(); - certificationRepository.save(certification); - - } catch (Exception e) { - log.info("이메일 인증 실패: {}", e); - return EmailCertificationResponseDto.databaseError(); - } - return EmailCertificationResponseDto.success(); - } - - - @Override - @Transactional - public ResponseEntity checkCertification(CheckCertificationInfo info) { - try { - String accountId = info.getAccountId(); - String certificationNumber = info.getCertificationNumber(); - - Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); - if (certification == null) return CheckCertificationResponseDto.certificationFail(); - - boolean isMatch = certification.getAccountId().equals(accountId) - && certification.getCertificationNumber().equals(certificationNumber); - - if (!isMatch) return CheckCertificationResponseDto.certificationFail(); - - // 인증 시간 검증 - LocalDateTime createdAt = certification.getCreatedAt(); - if (createdAt.isBefore(LocalDateTime.now().minusMinutes(5))) { // 5분 유효시간 - return CheckCertificationResponseDto.certificationExpired(); - } - - // 인증 완료 상태 업데이트 - certification.markAsVerified(); - certificationRepository.save(certification); - - } catch (Exception e) { - log.info("인증 번호 확인 실패: {}", e); - return CheckCertificationResponseDto.databaseError(); - } - - return CheckCertificationResponseDto.success(); - } - - - @Override - @Transactional - public ResponseEntity signUp(SignUpInfo info) { - try { - String accountId = info.getAccountId(); - String certificationNumber = info.getCertificationNumber(); - - // 1. 필수 정보 누락 확인 - if (accountId == null || certificationNumber == null || info.getPassword() == null) { - return SignUpResponseDto.missingRequiredFields(); - } - - // 2. 인증 번호 확인 - Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); - if (certification == null || !certification.getIsVerified()) { - return SignUpResponseDto.certificationFail(); // 인증되지 않은 경우 - } - - // 3. 중복된 ID 확인 - if (userRepository.existsByAccountId(accountId)) { - return SignUpResponseDto.duplicateId(); - } - - // 5. 비밀번호 암호화 후 저장 - String encodedPassword = passwordEncoder.encode(info.getPassword()); - - // 6. GeocodingService를 통해 지역명과 6자리 행정코드 가져오기 - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); - if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - return SignUpResponseDto.locationFail(); - } - - //6-1 Img 정제 - S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); - - // 7. 새로운 User 생성 및 저장 - User user = UserFactory.createNewUser(info, encodedPassword, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger(), petImg.uploadURL()); - userRepository.save(user); - - // 8. 인증 엔티티 삭제 - certificationRepository.deleteAllByAccountId(accountId); - - // 9. 성공 응답 반환 - return SignUpResponseDto.success(user.getId(), petImg.checkResultImg()); - - } catch (RuntimeException e) { - log.error("회원 가입 실패: {}", e.getMessage(), e); - throw e; - } catch (Exception e) { - log.error("회원 가입 중 처리되지 않은 예외 발생: {}", e.getMessage(), e); - return SignUpResponseDto.unknownError(); - } - } - - - @Override - public ResponseEntity signIn(SignInInfo info, HttpServletResponse response) { - try { - String accountId = info.getAccountId(); - User user = userRepository.findByAccountId(accountId); - // 사용자 존재 여부 확인 - if (user == null) { - log.info("사용자 조회 실패: {}", accountId); - return SignInResponseDto.signInFail(); - } - - // 비밀번호 확인 - String password = info.getPassword(); - String encodedPassword = user.getPassword(); - if (!passwordEncoder.matches(password, encodedPassword)) { - log.info("비밀번호 불일치: {}", accountId); - return SignInResponseDto.signInFail(); - } - - // JWT 생성 (userId를 subject로, accountId를 클레임으로 설정) - String token = jwtProvider.create(user.getId(), user.getAccountId()); - log.info("JWT 생성 완료: {}", token); - - ResponseCookie responseCookie = ResponseCookie.from("jwt", token) - .httpOnly(true) // XSS 방지 - .secure(true) // HTTPS만 허용 - .path("/") // 모든 경로에서 접근 가능 - .sameSite("None") // SameSite=None 설정 - .maxAge((3600)) - .build(); - response.addHeader("Set-Cookie", responseCookie.toString()); - // 로그인 성공 응답 반환 - return SignInResponseDto.success(user); // User 객체 전달 - } catch (Exception e) { - log.error("로그인 처리 중 예외 발생", e); - return SignInResponseDto.signInFail(); - } - } - - - @Override - public ResponseEntity logout(HttpServletResponse response) { - try { - // 만료된 쿠키 설정 - ResponseCookie expiredCookie = ResponseCookie.from("jwt", "") - .httpOnly(true) // XSS 방지 - .secure(true) // HTTPS만 허용 - .path("/") // 모든 경로에서 접근 가능 - .sameSite("None") // SameSite=None 설정 - .maxAge(0) // 즉시 만료 - .build(); - - response.addHeader("Set-Cookie", expiredCookie.toString()); - - log.info("JWT 쿠키 제거 및 로그아웃 처리 완료"); - return LogInResponseDto.success(); - } catch (Exception e) { - log.error("로그아웃 처리 중 예외 발생", e); - return LogInResponseDto.validationFail(); - } - } - - - @Override - @Transactional - public ResponseEntity deleteId(DeleteIdRequestDto dto) { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - boolean exists = userRepository.existsById(userId); - if (!exists) { - return DeleteIdResponseDto.idNotFound(); - } - - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - - // 비밀번호 일치 확인 - String password = dto.getPassword(); - String encodedPassword = user.getPassword(); - boolean isMatched = passwordEncoder.matches(password, encodedPassword); - if (!isMatched) return DeleteIdResponseDto.wrongPassword(); // 비밀번호 불일치 - - certificationRepository.deleteById(userId); - // 사용자 삭제 - List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 - // 명시적으로 Pet 삭제 - petRepository.deleteAll(pets); - userRepository.delete(user); - } catch (Exception e) { - log.info("회원 삭제 실패: {}", e); - return DeleteIdResponseDto.databaseError(); // 데이터베이스 오류 처리 - } - return DeleteIdResponseDto.success(); // 삭제 성공 응답 - } - - - @Override - public ResponseEntity getMypage() { - try { - String userId = jwtExtractProvider.findAccountIdFromJwt(); - User user = userRepository.findByAccountId(userId); - - boolean exists = userRepository.existsByAccountId(userId); - if (!exists) { - return GetMyProfileResponseDto.idNotFound(); - } - - return GetMyProfileResponseDto.success(user); - - } catch (Exception e) { - e.printStackTrace(); - return GetMyProfileResponseDto.databaseError(); - } - } - - - @Override - public ResponseEntity getOtherMypage(Long userId) { - try { - // 현재 로그인한 사용자 ID 가져오기 - Long myId = jwtExtractProvider.findIdFromJwt(); - if (!userRepository.existsById(myId)) { - return GetMyProfileResponseDto.idNotFound(); - } - - // 조회 대상 사용자 정보 가져오기 - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - // 조회 대상 사용자가 존재하는지 확인 - boolean exists = userRepository.existsById(userId); - if (!exists) { - return GetOtherProfileResponseDto.userNotFound(); - } - - // 현재 로그인한 사용자가 조회 대상 사용자를 찜했는지 확인 - boolean isMyHeartUser = heartRepository.existsByMyIdAndHeartedId(myId, userId); - - // 응답 생성 - return GetOtherProfileResponseDto.success(user, isMyHeartUser); - - } catch (Exception e) { - e.printStackTrace(); - return GetOtherProfileResponseDto.userNotFound(); - } - } - - - //TODO 고쳐야함. - @Override - @Transactional - public ResponseEntity editMyProfile(EditMyProfileInfo info) { - try { - System.out.println("info ::" + info.isCareAvailable()); - Long userId = jwtExtractProvider.findIdFromJwt(); - String userEmail = jwtExtractProvider.findAccountIdFromJwt(); - boolean exists = userRepository.existsById(userId); - if (!exists) { - return EditMyProfileResponseDto.idNotFound(); - } - - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - //6-1 Img 정제 - S3Imge petImg = awsClient.UploadImg(userEmail, info.getProfileImg(), "CUSTOM_USER_IMG", null); - - // 병합된 DTO를 기반으로 엔티티 생성 - String resultImg = user.updateImgURL(info.getProfileImg(), petImg); - user.updateProfile(info); - -// 반환해야 함 아래꺼 - return EditMyProfileResponseDto.success(resultImg); - - } catch (Exception e) { - log.info("프로필 수정 실패: {}", e); - return EditMyProfileResponseDto.editFailed(); - } - } - - - @Override - @Transactional - public ResponseEntity hearting(HeartingRequestDto dto) { - try { - Long heartedId = dto.getHeartedId(); - - // 대상 사용자가 존재하는지 확인 - boolean exists = userRepository.existsById(heartedId); - if (!exists) { - return HeartingResponseDto.heartedIdNotFound(); - } - - // 현재 사용자 ID 가져오기 - Long userId = jwtExtractProvider.findIdFromJwt(); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - // DB에서 myId와 heartedId로 Heart 레코드 확인 - Optional existingHeart = heartRepository.findByMyIdAndHeartedId(user.getId(), heartedId); - - if (existingHeart.isPresent()) { - // 찜하기 해제 (DB에서 삭제) - heartRepository.delete(existingHeart.get()); - return HeartingResponseDto.success(); // 찜하기 해제 성공 응답 - } - - // 찜하기 진행 (DB에 저장) - Heart heart = UserFactory.createHeart(userId, heartedId); - heartRepository.save(heart); - - return HeartingResponseDto.success(); // 찜하기 성공 응답 - - } catch (Exception e) { - log.info("찜하기 실패: {}", e); - return HeartingResponseDto.databaseError(); // 데이터베이스 오류 응답 - } - } - - - @Override - public ResponseEntity getHeartedList() { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - // Heart 리스트 조회 - List heartList = heartRepository.findAllByMyId(user.getId()); - - // Heart 정보와 관련된 User 데이터 매핑 - List heartedUsers = heartList.stream() - .map(heart -> { - User heartedUser = userRepository.findById(heart.getHeartedId()) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + heart.getHeartedId())); - - return new HeartedUserDto( - heart.getMyId(), - heart.getHeartedId(), - heartedUser.getNickname(), - heartedUser.getProfileImg(), - heartedUser.getCareAvailable(), - heartedUser.getPreferredSizes() - ); - }) - .toList(); - - // 성공 응답 반환 - return GetHeartingListResponseDto.success(heartedUsers); - - } catch (Exception e) { - log.info("찜리스트 받아오기 실패: {}", e); - return HeartingResponseDto.databaseError(); - } - } - - - @Override - @Transactional - public ResponseEntity sendRepassword(SendRepasswordRequestDto dto) { - try { - String accountId = dto.getAccountId(); - if (!userRepository.existsByAccountId(accountId)) { - return GetMyProfileResponseDto.idNotFound(); - } - User user = userRepository.findByAccountId(accountId); - - String rePasswordNum = RePasswordProvider.generatePassword(); - log.info("Generated Repassword: {}", rePasswordNum); - - boolean isSendSuccess = rePasswordEmailProvider.sendVerificationEmail(accountId, rePasswordNum); - if (!isSendSuccess) return SendRepasswordResponseDto.mailSendFail(); - - String encodedRePasswordNum = passwordEncoder.encode(rePasswordNum); - - user.updatePassword(encodedRePasswordNum); - - } catch (Exception e) { - log.info("임시비밀번호 재설정 실패: {}", e); - return SendRepasswordResponseDto.databaseError(); - } - return SendRepasswordResponseDto.success(); - } - - - @Override - @Transactional - public ResponseEntity repassword(RepasswordInfo info) { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - String currentPassword = info.getCurrentPassword(); - - boolean isPasswordValid = passwordEncoder.matches(currentPassword, user.getPassword()); - if (!isPasswordValid) { - return RepasswordResponseDto.wrongPassword(); - } - String newPassword = info.getNewPassword(); - - String encodedNewPassword = passwordEncoder.encode(newPassword); - - user.updatePassword(encodedNewPassword); - - } catch (Exception e) { - log.info("비밀번호 재설정 실패: {}", e); - return RepasswordResponseDto.databaseError(); - } - return RepasswordResponseDto.success(); - } - - - @Override - @Transactional - public ResponseEntity updateLocation(UpdateLocationInfo info) { - try { - // JWT에서 사용자 ID 추출 - Long userId = jwtExtractProvider.findIdFromJwt(); - - // 사용자 엔티티 조회 - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - // 사용자 존재 여부 확인 - boolean exists = userRepository.existsById(userId); - if (!exists) { - return UpdateLocationResponseDto.userNotFound(); - } - - // GeocodingService에서 지역명과 행정코드 가져오기 - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); - if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 - } - - user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - - // 성공 응답 반환 - return UpdateLocationResponseDto.success(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - } catch (Exception e) { - log.error("위치 업데이트 실패: {}", e.getMessage(), e); - return UpdateLocationResponseDto.wrongLocation(); - } - } - - @Override - @Transactional - public ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto) { - try { - Long recommendedId = dto.getUserId(); - Long myId = jwtExtractProvider.findIdFromJwt(); - User recommendedUser = userRepository.findById(recommendedId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + recommendedId)); - - boolean exists = userRepository.existsById(recommendedId); - if (!exists) { - return UpdateRecommendationResponseDto.userNotFound(); - } - Integer recommendationCount = recommendedUser.getRecommendationCount() + 1; - - recommendedUser.updateRecommendation(recommendationCount); - - Recommendation recommendation = UserFactory.createRecommendation(myId, recommendedId); - recommendationRepository.save(recommendation); - - } catch (Exception e) { - log.info("추천수 업데이트 실패: {}", e); - return UpdateRecommendationResponseDto.userNotFound(); - } - return UpdateRecommendationResponseDto.success(); - } - - @Override - public ResponseEntity getRecommend(UpdateRecommendationRequestDto dto) { - try { - Long recommendedId = dto.getUserId(); - Long myId = jwtExtractProvider.findIdFromJwt(); - - boolean exists = recommendationRepository.existsByMyIdAndRecommendedId(myId,recommendedId); - return GetRecommendationResponseDto.success(exists); - - } catch (Exception e) { - log.info("추천수 업데이트 실패: {}", e); - return GetRecommendationResponseDto.userNotFound(); - } - } - - @Override - @Transactional - public ResponseEntity editKakaoProfile(EditKakaoProfileInfo info) { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - - // userId가 null인 경우 예외 처리 - if (userId == null) { - log.warn("JWT에서 추출된 userId가 null입니다."); - return EditKakaoProfileResponseDto.idNotFound(); - } - - boolean exists = userRepository.existsById(userId); - if (!exists) { - log.warn("존재하지 않는 사용자 ID: {}", userId); - return EditKakaoProfileResponseDto.idNotFound(); - } - - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - GeocodingService.KakaoRegion kakaoRegion = geocodingService.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); - if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { - return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 - } - - user.updateKakaoProfile(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); - } catch (Exception e) { - log.error("프로필 수정 실패", e); - return EditKakaoProfileResponseDto.editFailed(); - } - return EditKakaoProfileResponseDto.success(); - } - - @Override - public GetMyUserDto receiverEmail(String accountId) { - try { - User user = userRepository.findByAccountId(accountId); - return new GetMyUserDto(user); - } catch (Exception e) { - e.printStackTrace(); - } - return new GetMyUserDto(); - } - - @Override - public void deleteUser(Long userUUID) { - userRepository.deleteUserById(userUUID); - } - - - public UserInfo selectUserInfo(String receiverEmail) { - User otherUser = userRepository.findByAccountId(receiverEmail); - return otherUser.of(); - } - - @Override - public String findByUserEmail(Long userId) { - return userRepository.findById(userId).get().getAccountId(); - } - -} +//package com.petmatz.domain.user.service; +// +//import com.petmatz.api.user.request.*; +//import com.petmatz.common.security.utils.JwtExtractProvider; +//import com.petmatz.common.security.utils.JwtProvider; +//import com.petmatz.domain.aws.AwsClient; +//import com.petmatz.domain.aws.vo.S3Imge; +//import com.petmatz.domain.pet.entity.Pet; +//import com.petmatz.domain.pet.repository.PetRepository; +//import com.petmatz.domain.user.component.GeocodingComponent; +//import com.petmatz.domain.user.entity.*; +//import com.petmatz.domain.user.info.*; +//import com.petmatz.domain.user.provider.CertificationNumberProvider; +//import com.petmatz.domain.user.provider.RePasswordProvider; +//import com.petmatz.domain.user.repository.CertificationRepository; +//import com.petmatz.domain.user.repository.HeartRepository; +//import com.petmatz.domain.user.repository.RecommendationRepository; +//import com.petmatz.domain.user.repository.UserRepository; +//import com.petmatz.domain.user.response.*; +//import com.petmatz.user.common.LogInResponseDto; +//import jakarta.servlet.http.HttpServletResponse; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.http.ResponseCookie; +//import org.springframework.http.ResponseEntity; +//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +//import org.springframework.security.crypto.password.PasswordEncoder; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +// +//import java.time.LocalDateTime; +//import java.util.List; +//import java.util.Optional; +// +//@Service +//@Slf4j +//@RequiredArgsConstructor +//public class UserServiceImpl implements UserService { +// +// private final UserRepository userRepository; +// private final CertificationRepository certificationRepository; +// private final HeartRepository heartRepository; +// private final RecommendationRepository recommendationRepository; +// private final JwtProvider jwtProvider; +// private final EmailProvider emailProvider; +// private final RePasswordEmailProvider rePasswordEmailProvider; +// private final PetRepository petRepository; +// +// private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); +// private final JwtExtractProvider jwtExtractProvider; +// +// private final GeocodingComponent geocodingComponent; +// +// private final AwsClient awsClient; +// +// +// @Override +// public ResponseEntity emailCertification(EmailCertificationRequestDto dto) { +// try { +// String accountId = dto.getAccountId(); +// +// //이메일 전송과 동시에 아이디 중복검사 +// boolean isExistId = userRepository.existsByAccountId(accountId); +// if (isExistId) return EmailCertificationResponseDto.duplicateId(); +// +// // 인증 번호 생성 및 이메일 전송 +// String certificationNumber = CertificationNumberProvider.generateNumber(); +// boolean isSendSuccess = emailProvider.sendVerificationEmail(accountId, certificationNumber); +// if (!isSendSuccess) return EmailCertificationResponseDto.mailSendFail(); +// +// // 인증 엔티티 저장 +// Certification certification = Certification.builder().accountId(accountId).certificationNumber(certificationNumber).isVerified(false).build(); +// certificationRepository.save(certification); +// +// } catch (Exception e) { +// log.info("이메일 인증 실패: {}", e); +// return EmailCertificationResponseDto.databaseError(); +// } +// return EmailCertificationResponseDto.success(); +// } +// +// +// @Override +// @Transactional +// public ResponseEntity checkCertification(CheckCertificationInfo info) { +// try { +// String accountId = info.getAccountId(); +// String certificationNumber = info.getCertificationNumber(); +// +// Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); +// if (certification == null) return CheckCertificationResponseDto.certificationFail(); +// +// boolean isMatch = certification.getAccountId().equals(accountId) +// && certification.getCertificationNumber().equals(certificationNumber); +// +// if (!isMatch) return CheckCertificationResponseDto.certificationFail(); +// +// // 인증 시간 검증 +// LocalDateTime createdAt = certification.getCreatedAt(); +// if (createdAt.isBefore(LocalDateTime.now().minusMinutes(5))) { // 5분 유효시간 +// return CheckCertificationResponseDto.certificationExpired(); +// } +// +// // 인증 완료 상태 업데이트 +// certification.markAsVerified(); +// certificationRepository.save(certification); +// +// } catch (Exception e) { +// log.info("인증 번호 확인 실패: {}", e); +// return CheckCertificationResponseDto.databaseError(); +// } +// +// return CheckCertificationResponseDto.success(); +// } +// +// +// @Override +// @Transactional +// public ResponseEntity signUp(SignUpInfo info) { +// try { +// String accountId = info.getAccountId(); +// String certificationNumber = info.getCertificationNumber(); +// +// // 1. 필수 정보 누락 확인 +// if (accountId == null || certificationNumber == null || info.getPassword() == null) { +// return SignUpResponseDto.missingRequiredFields(); +// } +// +// // 2. 인증 번호 확인 +// Certification certification = certificationRepository.findTopByAccountIdOrderByCreatedAtDesc(accountId); +// if (certification == null || !certification.getIsVerified()) { +// return SignUpResponseDto.certificationFail(); // 인증되지 않은 경우 +// } +// +// // 3. 중복된 ID 확인 +// if (userRepository.existsByAccountId(accountId)) { +// return SignUpResponseDto.duplicateId(); +// } +// +// // 5. 비밀번호 암호화 후 저장 +// String encodedPassword = passwordEncoder.encode(info.getPassword()); +// +// // 6. GeocodingService를 통해 지역명과 6자리 행정코드 가져오기 +// GeocodingComponent.KakaoRegion kakaoRegion = geocodingComponent.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); +// if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { +// return SignUpResponseDto.locationFail(); +// } +// +// //6-1 Img 정제 +// S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); +// +// // 7. 새로운 User 생성 및 저장 +// User user = UserFactory.createNewUser(info, encodedPassword, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger(), petImg.uploadURL()); +// userRepository.save(user); +// +// // 8. 인증 엔티티 삭제 +// certificationRepository.deleteAllByAccountId(accountId); +// +// // 9. 성공 응답 반환 +// return SignUpResponseDto.success(user.getId(), petImg.checkResultImg()); +// +// } catch (RuntimeException e) { +// log.error("회원 가입 실패: {}", e.getMessage(), e); +// throw e; +// } catch (Exception e) { +// log.error("회원 가입 중 처리되지 않은 예외 발생: {}", e.getMessage(), e); +// return SignUpResponseDto.unknownError(); +// } +// } +// +// +// @Override +// public ResponseEntity signIn(SignInInfo info, HttpServletResponse response) { +// try { +// String accountId = info.getAccountId(); +// User user = userRepository.findByAccountId(accountId); +// // 사용자 존재 여부 확인 +// if (user == null) { +// log.info("사용자 조회 실패: {}", accountId); +// return SignInResponseDto.signInFail(); +// } +// +// // 비밀번호 확인 +// String password = info.getPassword(); +// String encodedPassword = user.getPassword(); +// if (!passwordEncoder.matches(password, encodedPassword)) { +// log.info("비밀번호 불일치: {}", accountId); +// return SignInResponseDto.signInFail(); +// } +// +// // JWT 생성 (userId를 subject로, accountId를 클레임으로 설정) +// String token = jwtProvider.create(user.getId(), user.getAccountId()); +// log.info("JWT 생성 완료: {}", token); +// +// ResponseCookie responseCookie = ResponseCookie.from("jwt", token) +// .httpOnly(true) // XSS 방지 +// .secure(true) // HTTPS만 허용 +// .path("/") // 모든 경로에서 접근 가능 +// .sameSite("None") // SameSite=None 설정 +// .maxAge((3600)) +// .build(); +// response.addHeader("Set-Cookie", responseCookie.toString()); +// // 로그인 성공 응답 반환 +// return SignInResponseDto.success(user); // User 객체 전달 +// } catch (Exception e) { +// log.error("로그인 처리 중 예외 발생", e); +// return SignInResponseDto.signInFail(); +// } +// } +// +// +// @Override +// public ResponseEntity logout(HttpServletResponse response) { +// try { +// // 만료된 쿠키 설정 +// ResponseCookie expiredCookie = ResponseCookie.from("jwt", "") +// .httpOnly(true) // XSS 방지 +// .secure(true) // HTTPS만 허용 +// .path("/") // 모든 경로에서 접근 가능 +// .sameSite("None") // SameSite=None 설정 +// .maxAge(0) // 즉시 만료 +// .build(); +// +// response.addHeader("Set-Cookie", expiredCookie.toString()); +// +// log.info("JWT 쿠키 제거 및 로그아웃 처리 완료"); +// return LogInResponseDto.success(); +// } catch (Exception e) { +// log.error("로그아웃 처리 중 예외 발생", e); +// return LogInResponseDto.validationFail(); +// } +// } +// +// +// @Override +// @Transactional +// public ResponseEntity deleteId(DeleteIdRequestDto dto) { +// try { +// Long userId = jwtExtractProvider.findIdFromJwt(); +// boolean exists = userRepository.existsById(userId); +// if (!exists) { +// return DeleteIdResponseDto.idNotFound(); +// } +// +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// +// // 비밀번호 일치 확인 +// String password = dto.getPassword(); +// String encodedPassword = user.getPassword(); +// boolean isMatched = passwordEncoder.matches(password, encodedPassword); +// if (!isMatched) return DeleteIdResponseDto.wrongPassword(); // 비밀번호 불일치 +// +// certificationRepository.deleteById(userId); +// // 사용자 삭제 +// List pets = petRepository.findAllByUserId(user.getId()); // Pet 엔티티에서 User를 참조하는 기준으로 조회 +// // 명시적으로 Pet 삭제 +// petRepository.deleteAll(pets); +// userRepository.delete(user); +// } catch (Exception e) { +// log.info("회원 삭제 실패: {}", e); +// return DeleteIdResponseDto.databaseError(); // 데이터베이스 오류 처리 +// } +// return DeleteIdResponseDto.success(); // 삭제 성공 응답 +// } +// +// +// @Override +// public ResponseEntity getMypage() { +// try { +// String userId = jwtExtractProvider.findAccountIdFromJwt(); +// User user = userRepository.findByAccountId(userId); +// +// boolean exists = userRepository.existsByAccountId(userId); +// if (!exists) { +// return GetMyProfileResponseDto.idNotFound(); +// } +// +// return GetMyProfileResponseDto.success(user); +// +// } catch (Exception e) { +// e.printStackTrace(); +// return GetMyProfileResponseDto.databaseError(); +// } +// } +// +// +// @Override +// public ResponseEntity getOtherMypage(Long userId) { +// try { +// // 현재 로그인한 사용자 ID 가져오기 +// Long myId = jwtExtractProvider.findIdFromJwt(); +// if (!userRepository.existsById(myId)) { +// return GetMyProfileResponseDto.idNotFound(); +// } +// +// // 조회 대상 사용자 정보 가져오기 +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// // 조회 대상 사용자가 존재하는지 확인 +// boolean exists = userRepository.existsById(userId); +// if (!exists) { +// return GetOtherProfileResponseDto.userNotFound(); +// } +// +// // 현재 로그인한 사용자가 조회 대상 사용자를 찜했는지 확인 +// boolean isMyHeartUser = heartRepository.existsByMyIdAndHeartedId(myId, userId); +// +// // 응답 생성 +// return GetOtherProfileResponseDto.success(user, isMyHeartUser); +// +// } catch (Exception e) { +// e.printStackTrace(); +// return GetOtherProfileResponseDto.userNotFound(); +// } +// } +// +// +// //TODO 고쳐야함. +// @Override +// @Transactional +// public ResponseEntity editMyProfile(EditMyProfileInfo info) { +// try { +// System.out.println("info ::" + info.isCareAvailable()); +// Long userId = jwtExtractProvider.findIdFromJwt(); +// String userEmail = jwtExtractProvider.findAccountIdFromJwt(); +// boolean exists = userRepository.existsById(userId); +// if (!exists) { +// return EditMyProfileResponseDto.idNotFound(); +// } +// +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// //6-1 Img 정제 +// S3Imge petImg = awsClient.UploadImg(userEmail, info.getProfileImg(), "CUSTOM_USER_IMG", null); +// +// // 병합된 DTO를 기반으로 엔티티 생성 +// String resultImg = user.updateImgURL(info.getProfileImg(), petImg); +// user.updateProfile(info); +// +//// 반환해야 함 아래꺼 +// return EditMyProfileResponseDto.success(resultImg); +// +// } catch (Exception e) { +// log.info("프로필 수정 실패: {}", e); +// return EditMyProfileResponseDto.editFailed(); +// } +// } +// +// +// @Override +// @Transactional +// public ResponseEntity hearting(HeartingRequestDto dto) { +// try { +// Long heartedId = dto.getHeartedId(); +// +// // 대상 사용자가 존재하는지 확인 +// boolean exists = userRepository.existsById(heartedId); +// if (!exists) { +// return HeartingResponseDto.heartedIdNotFound(); +// } +// +// // 현재 사용자 ID 가져오기 +// Long userId = jwtExtractProvider.findIdFromJwt(); +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// // DB에서 myId와 heartedId로 Heart 레코드 확인 +// Optional existingHeart = heartRepository.findByMyIdAndHeartedId(user.getId(), heartedId); +// +// if (existingHeart.isPresent()) { +// // 찜하기 해제 (DB에서 삭제) +// heartRepository.delete(existingHeart.get()); +// return HeartingResponseDto.success(); // 찜하기 해제 성공 응답 +// } +// +// // 찜하기 진행 (DB에 저장) +// Heart heart = UserFactory.createHeart(userId, heartedId); +// heartRepository.save(heart); +// +// return HeartingResponseDto.success(); // 찜하기 성공 응답 +// +// } catch (Exception e) { +// log.info("찜하기 실패: {}", e); +// return HeartingResponseDto.databaseError(); // 데이터베이스 오류 응답 +// } +// } +// +// +// @Override +// public ResponseEntity getHeartedList() { +// try { +// Long userId = jwtExtractProvider.findIdFromJwt(); +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// // Heart 리스트 조회 +// List heartList = heartRepository.findAllByMyId(user.getId()); +// +// // Heart 정보와 관련된 User 데이터 매핑 +// List heartedUsers = heartList.stream() +// .map(heart -> { +// User heartedUser = userRepository.findById(heart.getHeartedId()) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + heart.getHeartedId())); +// +// return new HeartedUserDto( +// heart.getMyId(), +// heart.getHeartedId(), +// heartedUser.getNickname(), +// heartedUser.getProfileImg(), +// heartedUser.getCareAvailable(), +// heartedUser.getPreferredSizes() +// ); +// }) +// .toList(); +// +// // 성공 응답 반환 +// return GetHeartingListResponseDto.success(heartedUsers); +// +// } catch (Exception e) { +// log.info("찜리스트 받아오기 실패: {}", e); +// return HeartingResponseDto.databaseError(); +// } +// } +// +// +// @Override +// @Transactional +// public ResponseEntity sendRepassword(SendRepasswordRequestDto dto) { +// try { +// String accountId = dto.getAccountId(); +// if (!userRepository.existsByAccountId(accountId)) { +// return GetMyProfileResponseDto.idNotFound(); +// } +// User user = userRepository.findByAccountId(accountId); +// +// String rePasswordNum = RePasswordProvider.generatePassword(); +// log.info("Generated Repassword: {}", rePasswordNum); +// +//// boolean isSendSuccess = rePasswordEmailProvider.sendVerificationEmail(accountId, rePasswordNum); +//// if (!isSendSuccess) return SendRepasswordResponseDto.mailSendFail(); +// +// String encodedRePasswordNum = passwordEncoder.encode(rePasswordNum); +// +// user.updatePassword(encodedRePasswordNum); +// +// } catch (Exception e) { +// log.info("임시비밀번호 재설정 실패: {}", e); +// return SendRepasswordResponseDto.databaseError(); +// } +// return SendRepasswordResponseDto.success(); +// } +// +// +// @Override +// @Transactional +// public ResponseEntity repassword(RepasswordInfo info) { +// try { +// Long userId = jwtExtractProvider.findIdFromJwt(); +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// String currentPassword = info.getCurrentPassword(); +// +// boolean isPasswordValid = passwordEncoder.matches(currentPassword, user.getPassword()); +// if (!isPasswordValid) { +// return RepasswordResponseDto.wrongPassword(); +// } +// String newPassword = info.getNewPassword(); +// +// String encodedNewPassword = passwordEncoder.encode(newPassword); +// +// user.updatePassword(encodedNewPassword); +// +// } catch (Exception e) { +// log.info("비밀번호 재설정 실패: {}", e); +// return RepasswordResponseDto.databaseError(); +// } +// return RepasswordResponseDto.success(); +// } +// +// +// @Override +// @Transactional +// public ResponseEntity updateLocation(UpdateLocationInfo info) { +// try { +// // JWT에서 사용자 ID 추출 +// Long userId = jwtExtractProvider.findIdFromJwt(); +// +// // 사용자 엔티티 조회 +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// // 사용자 존재 여부 확인 +// boolean exists = userRepository.existsById(userId); +// if (!exists) { +// return UpdateLocationResponseDto.userNotFound(); +// } +// +// // GeocodingService에서 지역명과 행정코드 가져오기 +// GeocodingComponent.KakaoRegion kakaoRegion = geocodingComponent.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); +// if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { +// return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 +// } +// +// user.updateLocation(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); +// +// // 성공 응답 반환 +// return UpdateLocationResponseDto.success(kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); +// } catch (Exception e) { +// log.error("위치 업데이트 실패: {}", e.getMessage(), e); +// return UpdateLocationResponseDto.wrongLocation(); +// } +// } +// +// @Override +// @Transactional +// public ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto) { +// try { +// Long recommendedId = dto.getUserId(); +// Long myId = jwtExtractProvider.findIdFromJwt(); +// User recommendedUser = userRepository.findById(recommendedId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + recommendedId)); +// +// boolean exists = userRepository.existsById(recommendedId); +// if (!exists) { +// return UpdateRecommendationResponseDto.userNotFound(); +// } +// Integer recommendationCount = recommendedUser.getRecommendationCount() + 1; +// +// recommendedUser.updateRecommendation(recommendationCount); +// +// Recommendation recommendation = UserFactory.createRecommendation(myId, recommendedId); +// recommendationRepository.save(recommendation); +// +// } catch (Exception e) { +// log.info("추천수 업데이트 실패: {}", e); +// return UpdateRecommendationResponseDto.userNotFound(); +// } +// return UpdateRecommendationResponseDto.success(); +// } +// +// @Override +// public ResponseEntity getRecommend(UpdateRecommendationRequestDto dto) { +// try { +// Long recommendedId = dto.getUserId(); +// Long myId = jwtExtractProvider.findIdFromJwt(); +// +// boolean exists = recommendationRepository.existsByMyIdAndRecommendedId(myId,recommendedId); +// return GetRecommendationResponseDto.success(exists); +// +// } catch (Exception e) { +// log.info("추천수 업데이트 실패: {}", e); +// return GetRecommendationResponseDto.userNotFound(); +// } +// } +// +// @Override +// @Transactional +// public ResponseEntity editKakaoProfile(EditKakaoProfileInfo info) { +// try { +// Long userId = jwtExtractProvider.findIdFromJwt(); +// +// // userId가 null인 경우 예외 처리 +// if (userId == null) { +// log.warn("JWT에서 추출된 userId가 null입니다."); +// return EditKakaoProfileResponseDto.idNotFound(); +// } +// +// boolean exists = userRepository.existsById(userId); +// if (!exists) { +// log.warn("존재하지 않는 사용자 ID: {}", userId); +// return EditKakaoProfileResponseDto.idNotFound(); +// } +// +// User user = userRepository.findById(userId) +// .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); +// +// GeocodingComponent.KakaoRegion kakaoRegion = geocodingComponent.getRegionFromCoordinates(info.getLatitude(), info.getLongitude()); +// if (kakaoRegion == null || kakaoRegion.getCodeAsInteger() == null) { +// return UpdateLocationResponseDto.wrongLocation(); // Kakao API 호출 실패 처리 +// } +// +// user.updateKakaoProfile(info, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger()); +// } catch (Exception e) { +// log.error("프로필 수정 실패", e); +// return EditKakaoProfileResponseDto.editFailed(); +// } +// return EditKakaoProfileResponseDto.success(); +// } +// +// @Override +// public GetMyUserDto receiverEmail(String accountId) { +// try { +// User user = userRepository.findByAccountId(accountId); +// return new GetMyUserDto(user); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return new GetMyUserDto(); +// } +// +// @Override +// public void deleteUser(Long userUUID) { +// userRepository.deleteUserById(userUUID); +// } +// +// +// public UserInfo selectUserInfo(String receiverEmail) { +// User otherUser = userRepository.findByAccountId(receiverEmail); +// return otherUser.of(); +// } +// +// @Override +// public String findByUserEmail(Long userId) { +// return userRepository.findById(userId).get().getAccountId(); +// } +// +//} diff --git a/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java b/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java index 15aedfa..a0502cc 100644 --- a/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java +++ b/src/main/java/com/petmatz/infra/email/EmailProviderImpl.java @@ -1,5 +1,6 @@ package com.petmatz.infra.email; +import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.service.EmailProvider; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; @@ -10,6 +11,8 @@ import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; +import static com.petmatz.domain.user.exception.MatchErrorCode.FAIL_MAIL_SEND; + @Component @RequiredArgsConstructor @Slf4j @@ -27,8 +30,7 @@ public class EmailProviderImpl implements EmailProvider { * @return 이메일 전송 성공 여부 (true: 성공, false: 실패) */ @Override - public boolean sendVerificationEmail(String email, String certificationNumber) { - + public void sendVerificationEmail(String email, String certificationNumber) { try { System.out.println(certificationNumber); // MIME 타입의 이메일 메시지 생성 @@ -47,11 +49,8 @@ public boolean sendVerificationEmail(String email, String certificationNumber) { javaMailSender.send(message); } catch (Exception e) { - log.info("이메일 {} 로 전송 실패", email); - log.info("Error: {}", e); - return false; + throw new UserException(FAIL_MAIL_SEND); } - return true; // 전송 성공 시 true 리턴 } /** diff --git a/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java b/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java index b7e9741..4a87bba 100644 --- a/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java +++ b/src/main/java/com/petmatz/infra/email/RePasswordEmailProviderImpl.java @@ -1,5 +1,6 @@ package com.petmatz.infra.email; +import com.petmatz.domain.user.exception.UserException; import com.petmatz.domain.user.service.RePasswordEmailProvider; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; @@ -10,6 +11,8 @@ import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; +import static com.petmatz.domain.user.exception.MatchErrorCode.FAIL_MAIL_SEND; + @Component @RequiredArgsConstructor @Slf4j @@ -21,8 +24,7 @@ public class RePasswordEmailProviderImpl implements RePasswordEmailProvider { @Override - public boolean sendVerificationEmail(String email, String rePassword) { - + public void sendVerificationEmail(String email, String rePassword) { try { // MIME 타입의 이메일 메시지 생성 MimeMessage message = javaMailSender.createMimeMessage(); @@ -40,11 +42,8 @@ public boolean sendVerificationEmail(String email, String rePassword) { javaMailSender.send(message); } catch (Exception e) { - log.info("이메일 {} 로 전송 실패", email); - log.info("Error: {}", e); - return false; + throw new UserException(FAIL_MAIL_SEND); // 추후에 메일 예외로 변경 } - return true; // 전송 성공 시 true 리턴 } From 706f3e4bae203d12f01c4235f6ebc1445e59f523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Fri, 10 Jan 2025 01:11:18 +0900 Subject: [PATCH 11/13] =?UTF-8?q?Refactor=20:=201=EC=B0=A8=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20|=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B6=84=EB=A6=AC=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=99=84=EB=A3=8C!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/component/PasswordComponent.java | 77 +++---------------- 1 file changed, 11 insertions(+), 66 deletions(-) diff --git a/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java b/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java index f3e03e2..bd743c9 100644 --- a/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/PasswordComponent.java @@ -1,82 +1,27 @@ package com.petmatz.domain.user.component; -import com.petmatz.api.user.request.SendRepasswordRequestDto; -import com.petmatz.common.security.utils.JwtExtractProvider; -import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.info.RepasswordInfo; -import com.petmatz.domain.user.provider.RePasswordProvider; -import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.GetMyProfileResponseDto; -import com.petmatz.domain.user.response.RepasswordResponseDto; -import com.petmatz.domain.user.response.SendRepasswordResponseDto; -import com.petmatz.domain.user.service.RePasswordEmailProvider; +import com.petmatz.domain.user.exception.UserException; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; + +import static com.petmatz.domain.user.exception.MatchErrorCode.PASSWORD_MISMATCH; @Component @RequiredArgsConstructor -@Slf4j public class PasswordComponent { - private final UserRepository userRepository; - private final RePasswordEmailProvider rePasswordEmailProvider; - private final JwtExtractProvider jwtExtractProvider; - private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - - @Transactional - public ResponseEntity sendRepassword(SendRepasswordRequestDto dto) { - try { - String accountId = dto.getAccountId(); - if (!userRepository.existsByAccountId(accountId)) { - return GetMyProfileResponseDto.idNotFound(); - } - User user = userRepository.findByAccountId(accountId); - - String rePasswordNum = RePasswordProvider.generatePassword(); - log.info("Generated Repassword: {}", rePasswordNum); - - boolean isSendSuccess = rePasswordEmailProvider.sendVerificationEmail(accountId, rePasswordNum); - if (!isSendSuccess) return SendRepasswordResponseDto.mailSendFail(); - - String encodedRePasswordNum = passwordEncoder.encode(rePasswordNum); - - user.updatePassword(encodedRePasswordNum); + private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - } catch (Exception e) { - log.info("임시비밀번호 재설정 실패: {}", e); - return SendRepasswordResponseDto.databaseError(); - } - return SendRepasswordResponseDto.success(); + public String encodePassword(String repasswordNum) { + passwordEncoder.encode(repasswordNum); + return repasswordNum; } - @Transactional - public ResponseEntity repassword(RepasswordInfo info) { - try { - Long userId = jwtExtractProvider.findIdFromJwt(); - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found for ID: " + userId)); - - String currentPassword = info.getCurrentPassword(); - - boolean isPasswordValid = passwordEncoder.matches(currentPassword, user.getPassword()); - if (!isPasswordValid) { - return RepasswordResponseDto.wrongPassword(); - } - String newPassword = info.getNewPassword(); - - String encodedNewPassword = passwordEncoder.encode(newPassword); - - user.updatePassword(encodedNewPassword); - - } catch (Exception e) { - log.info("비밀번호 재설정 실패: {}", e); - return RepasswordResponseDto.databaseError(); + public void validatePassword(String currentPassword, String userPassword) { + if (!passwordEncoder.matches(currentPassword, userPassword)) { + throw new UserException(PASSWORD_MISMATCH); } - return RepasswordResponseDto.success(); } -} +} \ No newline at end of file From b3d4b7ac39f56d2068ecab8e86c2aa77c3dbabed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Wed, 15 Jan 2025 01:12:23 +0900 Subject: [PATCH 12/13] =?UTF-8?q?Refactor=20:=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=97=AC=EB=9F=AC=EA=B0=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=82=98=EB=88=88=EB=92=A4=EC=97=90=20=EA=B0=81=EA=B0=81?= =?UTF-8?q?=EC=9D=98=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=A0=84?= =?UTF-8?q?=EB=B6=80=20=EA=B5=AC=ED=98=84=20(=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20x)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/user/controller/AuthController.java | 59 +++++++ .../api/user/controller/EmailController.java | 28 ++++ .../api/user/controller/HeartController.java | 33 ++++ .../user/controller/LocationController.java | 25 +++ .../api/user/controller/PageController.java | 39 +++++ .../user/controller/PasswordController.java | 34 ++++ .../user/controller/RecommendController.java | 33 ++++ .../api/user/controller/UserController.java | 147 ++---------------- .../domain/user/component/KakaoComponent.java | 7 +- .../{UserComponent.java => UserService.java} | 22 +-- .../domain/user/service/AuthService.java | 7 +- .../KakaoUserService.java} | 6 +- .../PageService.java} | 13 +- .../domain/user/service/UserService.java | 33 ++-- 14 files changed, 301 insertions(+), 185 deletions(-) create mode 100644 src/main/java/com/petmatz/api/user/controller/AuthController.java create mode 100644 src/main/java/com/petmatz/api/user/controller/EmailController.java create mode 100644 src/main/java/com/petmatz/api/user/controller/HeartController.java create mode 100644 src/main/java/com/petmatz/api/user/controller/LocationController.java create mode 100644 src/main/java/com/petmatz/api/user/controller/PageController.java create mode 100644 src/main/java/com/petmatz/api/user/controller/PasswordController.java create mode 100644 src/main/java/com/petmatz/api/user/controller/RecommendController.java rename src/main/java/com/petmatz/domain/user/component/{UserComponent.java => UserService.java} (74%) rename src/main/java/com/petmatz/domain/user/{component/KakaoUserComponent.java => service/KakaoUserService.java} (92%) rename src/main/java/com/petmatz/domain/user/{component/PageComponent.java => service/PageService.java} (89%) diff --git a/src/main/java/com/petmatz/api/user/controller/AuthController.java b/src/main/java/com/petmatz/api/user/controller/AuthController.java new file mode 100644 index 0000000..4d855e6 --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/AuthController.java @@ -0,0 +1,59 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.CheckCertificationRequestDto; +import com.petmatz.api.user.request.DeleteIdRequestDto; +import com.petmatz.api.user.request.SignInRequestDto; +import com.petmatz.api.user.request.SignUpRequestDto; +import com.petmatz.domain.user.entity.User; +import com.petmatz.domain.user.response.CheckCertificationResponseDto; +import com.petmatz.domain.user.response.DeleteIdResponseDto; +import com.petmatz.domain.user.response.SignInResponseDto; +import com.petmatz.domain.user.response.SignUpResponseDto; +import com.petmatz.domain.user.service.AuthService; +import com.petmatz.domain.user.service.UserService; +import com.petmatz.user.common.LogInResponseDto; +import jakarta.servlet.http.HttpServletResponse; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.naming.AuthenticationException; +import java.net.MalformedURLException; +import java.security.cert.CertificateException; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/auth") +public class AuthController { + + private final AuthService authService; + + @PostMapping("/sign-in") + public Response signIn(@RequestBody @Valid SignInRequestDto requestBody, HttpServletResponse response) throws AuthenticationException { + SignInResponseDto result = authService.signIn(SignInRequestDto.of(requestBody), response); + return Response.success(result); + } + + @PostMapping("/logout") + public Response logout(HttpServletResponse res) { + authService.logout(res); + return Response.success(); + } + + @PostMapping("/sign-up") + public Response signUp(@RequestBody @Valid SignUpRequestDto requestBody) throws MalformedURLException { + SignUpResponseDto responseDto = authService.signUp(SignUpRequestDto.of(requestBody)); + return Response.success(responseDto); + } + + @PostMapping("/check-certification") + public Response checkCertification(@RequestBody @Valid CheckCertificationRequestDto requestBody) throws CertificateException { + authService.checkCertification(CheckCertificationRequestDto.of(requestBody)); + return Response.success(); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/EmailController.java b/src/main/java/com/petmatz/api/user/controller/EmailController.java new file mode 100644 index 0000000..eec20c7 --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/EmailController.java @@ -0,0 +1,28 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.EmailCertificationRequestDto; +import com.petmatz.domain.user.response.EmailCertificationResponseDto; +import com.petmatz.domain.user.service.EmailService; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/auth") +public class EmailController { + + private final EmailService emailService; + + @PostMapping("/email-certification") + public Response emailCertification(@RequestBody @Valid EmailCertificationRequestDto requestBody) { + emailService.emailCertification(requestBody); + return Response.success(); + } + +} diff --git a/src/main/java/com/petmatz/api/user/controller/HeartController.java b/src/main/java/com/petmatz/api/user/controller/HeartController.java new file mode 100644 index 0000000..6434288 --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/HeartController.java @@ -0,0 +1,33 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.HeartedUserDto; +import com.petmatz.api.user.request.HeartingRequestDto; +import com.petmatz.domain.user.service.HeartService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class HeartController { + + private final HeartService heartService; + + @PostMapping("/hearting") + public Response hearting(@RequestBody @Valid HeartingRequestDto requestBody) { + heartService.hearting(requestBody); + return Response.success(); + } + + @GetMapping("/get-heartlist") // 이거 테스트 필요 + public Response> getHeartedList() { + List heartedList = heartService.getHeartedList(); + return Response.success(heartedList); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/LocationController.java b/src/main/java/com/petmatz/api/user/controller/LocationController.java new file mode 100644 index 0000000..e5148e3 --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/LocationController.java @@ -0,0 +1,25 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.UpdateLocationRequestDto; +import com.petmatz.domain.user.response.UpdateLocationResponseDto; +import com.petmatz.domain.user.service.LocationService; +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; + +@RestController +@RequiredArgsConstructor +public class LocationController { + + private final LocationService locationService; + + @PostMapping("/update-location") + public Response updateLocation(@RequestBody @Valid UpdateLocationRequestDto requestBody) { + UpdateLocationResponseDto updateLocationResponseDto = locationService.updateLocation(UpdateLocationRequestDto.of(requestBody)); + return Response.success(updateLocationResponseDto); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/PageController.java b/src/main/java/com/petmatz/api/user/controller/PageController.java new file mode 100644 index 0000000..8f7255b --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/PageController.java @@ -0,0 +1,39 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.EditMyProfileRequestDto; +import com.petmatz.domain.user.service.PageService; +import com.petmatz.domain.user.response.EditMyProfileResponseDto; +import com.petmatz.domain.user.response.GetMyProfileResponseDto; +import com.petmatz.domain.user.response.GetOtherProfileResponseDto; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.net.MalformedURLException; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/auth") +public class PageController { + + private final PageService pageService; + + @GetMapping("/get-myprofile") + public Response getMypage() { + GetMyProfileResponseDto myPage = pageService.getMypage(); + return Response.success(myPage); + } + + @GetMapping("/get-otherprofile") + public Response getOtherMypage(@RequestParam @Valid Long userId) { + GetOtherProfileResponseDto otherMypage = pageService.getOtherMypage(userId); + return Response.success(otherMypage); + } + + @PostMapping("/edit-myprofile") + public Response editMyProfile(@RequestBody @Valid EditMyProfileRequestDto requestBody) throws MalformedURLException { + EditMyProfileResponseDto editMyProfileResponseDto = pageService.editMyProfile(EditMyProfileRequestDto.of(requestBody)); + return Response.success(editMyProfileResponseDto); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/PasswordController.java b/src/main/java/com/petmatz/api/user/controller/PasswordController.java new file mode 100644 index 0000000..d02a359 --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/PasswordController.java @@ -0,0 +1,34 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.RepasswordRequestDto; +import com.petmatz.api.user.request.SendRepasswordRequestDto; +import com.petmatz.domain.user.component.PasswordService; +import com.petmatz.domain.user.response.RepasswordResponseDto; +import com.petmatz.domain.user.response.SendRepasswordResponseDto; +import com.petmatz.domain.user.service.PasswordService; +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; + +@RestController +@RequiredArgsConstructor +public class PasswordController { + + private final PasswordService passwordService; + + @PostMapping("/send-repassword") + public Response sendRepassword(@RequestBody @Valid SendRepasswordRequestDto requestBody) { + SendRepasswordResponseDto sendRepasswordResponseDto = passwordService.sendRepassword(requestBody); + return Response.success(sendRepasswordResponseDto); + } + + @PostMapping("/repassword") + public Response repassword(@RequestBody @Valid RepasswordRequestDto requestBody) { + passwordService.repassword(RepasswordRequestDto.of(requestBody)); + return Response.success(); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/RecommendController.java b/src/main/java/com/petmatz/api/user/controller/RecommendController.java new file mode 100644 index 0000000..628c556 --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/RecommendController.java @@ -0,0 +1,33 @@ +package com.petmatz.api.user.controller; + +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.UpdateRecommendationRequestDto; +import com.petmatz.domain.user.response.GetRecommendationResponseDto; +import com.petmatz.domain.user.response.UpdateRecommendationResponseDto; +import com.petmatz.domain.user.service.RecommendService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class RecommendController { + + private final RecommendService recommendService; + + @PostMapping("/update-recommendation") + public Response updateRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { + recommendService.updateRecommend(requestBody); + return Response.success(); + } + + @GetMapping("/get-recommended") + public Response getRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { + GetRecommendationResponseDto recommend = recommendService.getRecommend(requestBody); + return Response.success(recommend); + } +} diff --git a/src/main/java/com/petmatz/api/user/controller/UserController.java b/src/main/java/com/petmatz/api/user/controller/UserController.java index 046f1b0..2c35b30 100644 --- a/src/main/java/com/petmatz/api/user/controller/UserController.java +++ b/src/main/java/com/petmatz/api/user/controller/UserController.java @@ -1,150 +1,33 @@ package com.petmatz.api.user.controller; -import com.petmatz.api.user.request.*; -import com.petmatz.domain.user.service.AuthService; -import com.petmatz.domain.user.response.*; -import com.petmatz.domain.user.service.UserService; -import com.petmatz.user.common.LogInResponseDto; -import jakarta.servlet.http.HttpServletResponse; +import com.petmatz.api.global.dto.Response; +import com.petmatz.api.user.request.DeleteIdRequestDto; +import com.petmatz.api.user.request.EditKakaoProfileRequestDto; +import com.petmatz.domain.user.service.KakaoUserService; +import com.petmatz.domain.user.component.UserService; +import com.petmatz.domain.user.response.EditKakaoProfileResponseDto; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.net.MalformedURLException; - -@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/api/auth") public class UserController { - private final UserService userService; - private final AuthService authService; - - @PostMapping("/email-certification") - public ResponseEntity emailCertification(@RequestBody @Valid EmailCertificationRequestDto requestBody) { - ResponseEntity response = userService.emailCertification(requestBody); - log.info("[emailCertification]: { accountId: " + requestBody.getAccountId() + "}"); - return response; - } - - @PostMapping("/check-certification") - public ResponseEntity checkCertification(@RequestBody @Valid CheckCertificationRequestDto requestBody) { - ResponseEntity response = userService.checkCertification(CheckCertificationRequestDto.of(requestBody)); - log.info("[checkCertification]: {accountId: " + requestBody.getAccountId() + ", certificationNumber: " + requestBody.getCertificationNumber() + "}"); - return response; - } - - @PostMapping("/sign-up") - public ResponseEntity signUp(@RequestBody @Valid SignUpRequestDto requestBody) throws MalformedURLException { - SignUpResponseDto responseDto = authService.signUp(SignUpRequestDto.of(requestBody)); - log.info("[signUp]: { accountId: " + requestBody.getAccountId() + ", password: " + requestBody.getPassword()); - return ResponseEntity.ok(responseDto); - } - - @PostMapping("/sign-in") - public ResponseEntity signIn( - @RequestBody @Valid SignInRequestDto requestBody, - HttpServletResponse response) { - ResponseEntity result = userService.signIn(SignInRequestDto.of(requestBody), response); - log.info("[signIn]: { accountId: " + requestBody.getAccountId() + ", result: " + result.getStatusCode() + " }"); - return result; - } - - @PostMapping("/delete-user") - public ResponseEntity deleteUser(@RequestBody @Valid DeleteIdRequestDto requestBody) { - ResponseEntity response = userService.deleteId(requestBody); - log.info("[deleteUser]:{password: " + requestBody.getPassword() + "}"); - return response; - } - - //---------------------------------------------------------------------------------------------------------------------------------// - @GetMapping("/get-myprofile") - public ResponseEntity getMypage() { - ResponseEntity response = userService.getMypage(); - log.info("[getMypage]"); - return response; - } - - @GetMapping("/get-otherprofile") - public ResponseEntity getOtherMypage(@RequestParam @Valid Long userId) { - ResponseEntity response = userService.getOtherMypage(userId); - log.info("[getOtherMypage]"); - return response; - } - - @PostMapping("/edit-myprofile") - public ResponseEntity editMyProfile(@RequestBody @Valid EditMyProfileRequestDto requestBody) { - ResponseEntity response = userService.editMyProfile(EditMyProfileRequestDto.of(requestBody)); - log.info("[editMyProfile]"); - return response; - } - - @PostMapping("/send-repassword") - public ResponseEntity sendRepassword(@RequestBody @Valid SendRepasswordRequestDto requestBody) { - ResponseEntity response = userService.sendRepassword(requestBody); - log.info("[sendRepassword]: {accountId: " + requestBody.getAccountId() + "}"); - return response; - } - - @PostMapping("/repassword") - public ResponseEntity repassword(@RequestBody @Valid RepasswordRequestDto requestBody) { - ResponseEntity response = userService.repassword(RepasswordRequestDto.of(requestBody)); - log.info("[repassword]: {currentPassword: " + requestBody.getCurrentPassword() + ", newPassword: " + requestBody.getNewPassword() + "}"); - return response; - } - - //--------------------------------------------------------------------------------------------------------------------------------------------------------------- - @PostMapping("/hearting") - public ResponseEntity hearting(@RequestBody @Valid HeartingRequestDto requestBody) { - ResponseEntity response = userService.hearting(requestBody); - log.info("[hearting]: {heartedId: " + requestBody.getHeartedId() + "}"); - return response; - } - - @GetMapping("/get-heartlist") - public ResponseEntity getHeartedList() { - ResponseEntity response = userService.getHeartedList(); - log.info("[getHeartedList]"); - return response; - } - - @PostMapping("/update-location") - public ResponseEntity updateLocation(@RequestBody @Valid UpdateLocationRequestDto requestBody) { - ResponseEntity response = userService.updateLocation(UpdateLocationRequestDto.of(requestBody)); - log.info("[updateLocation]"); - return response; - } - - @PostMapping("/update-recommendation") - public ResponseEntity updateRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { - ResponseEntity response = userService.updateRecommend(requestBody); - log.info("[updateRecommend]"); - return response; - } - - @GetMapping("/get-recommended") - public ResponseEntity getRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { - ResponseEntity response = userService.getRecommend(requestBody); - log.info("[getRecommend]"); - return response; - } + private final UserService userService; + private final KakaoUserService kakaoUserService; - @PostMapping("/logout") - public ResponseEntity logout(HttpServletResponse res) { - ResponseEntity response = userService.logout(res); - log.info("[logout]"); - return response; + @DeleteMapping("/delete-user") + public Response deleteUser(@RequestBody @Valid DeleteIdRequestDto requestBody) { + userService.deleteId(requestBody); + return Response.success(); } @PostMapping("/edit-kakaoprofile") - public ResponseEntity editKakaoProfile(@RequestBody @Valid EditKakaoProfileRequestDto requestBody) { - ResponseEntity response = userService.editKakaoProfile(EditKakaoProfileRequestDto.of(requestBody)); - log.info("[editKakaoProfile]"); - return response; + public Response editKakaoProfile(@RequestBody @Valid EditKakaoProfileRequestDto requestBody) { + kakaoUserService.editKakaoProfile(EditKakaoProfileRequestDto.of(requestBody)); + return Response.success(); } - } - diff --git a/src/main/java/com/petmatz/domain/user/component/KakaoComponent.java b/src/main/java/com/petmatz/domain/user/component/KakaoComponent.java index e6fbccd..2571dd9 100644 --- a/src/main/java/com/petmatz/domain/user/component/KakaoComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/KakaoComponent.java @@ -3,6 +3,7 @@ import com.petmatz.domain.user.entity.CustomOAuthUser; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.repository.UserRepository; +import com.petmatz.domain.user.service.KakaoUserService; import lombok.RequiredArgsConstructor; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -17,7 +18,7 @@ public class KakaoComponent extends DefaultOAuth2UserService implements OAuth2UserLoader { private final UserRepository userRepository; - private final KakaoUserComponent kaKaoUserComponent; + private final KakaoUserService kaKaoUserService; @Override public boolean supports(String registrationId) { return "kakao".equalsIgnoreCase(registrationId); @@ -53,11 +54,9 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic User user = userRepository.findByAccountId(email); if (user == null) { - user = kaKaoUserComponent.createNewKakaoUser(kakaoAccountId, email, nickname, profileImage); + user = kaKaoUserService.createNewKakaoUser(kakaoAccountId, email, nickname, profileImage); } return new CustomOAuthUser(user.getId(), user.getAccountId(), attributes, oAuth2User.getAuthorities()); } - - } diff --git a/src/main/java/com/petmatz/domain/user/component/UserComponent.java b/src/main/java/com/petmatz/domain/user/component/UserService.java similarity index 74% rename from src/main/java/com/petmatz/domain/user/component/UserComponent.java rename to src/main/java/com/petmatz/domain/user/component/UserService.java index ff5be8f..41fe880 100644 --- a/src/main/java/com/petmatz/domain/user/component/UserComponent.java +++ b/src/main/java/com/petmatz/domain/user/component/UserService.java @@ -2,22 +2,14 @@ import com.petmatz.api.user.request.DeleteIdRequestDto; import com.petmatz.common.security.utils.JwtExtractProvider; -import com.petmatz.common.security.utils.JwtProvider; -import com.petmatz.domain.pet.component.PetReader; import com.petmatz.domain.pet.entity.Pet; import com.petmatz.domain.pet.repository.PetRepository; import com.petmatz.domain.user.entity.User; -import com.petmatz.domain.user.info.UpdateLocationInfo; import com.petmatz.domain.user.info.UserInfo; import com.petmatz.domain.user.repository.CertificationRepository; import com.petmatz.domain.user.repository.UserRepository; -import com.petmatz.domain.user.response.DeleteIdResponseDto; -import com.petmatz.domain.user.response.UpdateLocationResponseDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -26,16 +18,18 @@ @Component @RequiredArgsConstructor @Slf4j -public class UserComponent { +public class UserService { /** - * pet 레포에서 꺼내오는 거 펫 도메인으로 옮겨야함. + * 여기 종원님이랑 머지하고 펫레포 최신버전 받아오고 수정 예정 25.01.14 */ private final UserRepository userRepository; + private final PetRepository petRepository; + + private final CertificationRepository certificationRepository; private final JwtExtractProvider jwtExtractProvider; - private final PetRepository petRepository; private final UserUtils userUtils; private final PasswordComponent passwordComponent; @@ -55,12 +49,8 @@ public void deleteId(DeleteIdRequestDto dto) { // 명시적으로 Pet 삭제 petRepository.deleteAll(pets); userRepository.delete(user); - /** - * 여기 종원님이랑 머지하고 펫레포에서 지우는거 만들어달라고 의뢰 예정 - */ } - - + public UserInfo selectUserInfo(String receiverEmail) { User otherUser = userRepository.findByAccountId(receiverEmail); diff --git a/src/main/java/com/petmatz/domain/user/service/AuthService.java b/src/main/java/com/petmatz/domain/user/service/AuthService.java index 885a070..4d1bc78 100644 --- a/src/main/java/com/petmatz/domain/user/service/AuthService.java +++ b/src/main/java/com/petmatz/domain/user/service/AuthService.java @@ -63,17 +63,16 @@ public SignUpResponseDto signUp(SignUpInfo info) throws MalformedURLException { // 지역명과 6자리 행정코드 가져오기 KakaoRegion kakaoRegion = geocodingComponent.getValidRegion(info.getLatitude(), info.getLongitude()); - //6-1 Img 정제 + //Img 정제 S3Imge petImg = awsClient.UploadImg(info.getAccountId(), info.getProfileImg(), "CUSTOM_USER_IMG", null); - // 7. 새로운 User 생성 및 저장 + //새로운 User 생성 및 저장 User user = UserFactory.createNewUser(info, encodedPassword, kakaoRegion.getRegionName(), kakaoRegion.getCodeAsInteger(), petImg.uploadURL()); userRepository.save(user); - // 8. 인증 엔티티 삭제 + //인증 엔티티 삭제 certificationRepository.deleteAllByAccountId(accountId); - // 9. 성공 응답 반환 return new SignUpResponseDto(user.getId(), petImg.checkResultImg()); } diff --git a/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java b/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java similarity index 92% rename from src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java rename to src/main/java/com/petmatz/domain/user/service/KakaoUserService.java index 7107762..e04dd27 100644 --- a/src/main/java/com/petmatz/domain/user/component/KakaoUserComponent.java +++ b/src/main/java/com/petmatz/domain/user/service/KakaoUserService.java @@ -1,6 +1,8 @@ -package com.petmatz.domain.user.component; +package com.petmatz.domain.user.service; import com.petmatz.common.security.utils.JwtExtractProvider; +import com.petmatz.domain.user.component.GeocodingComponent; +import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.constant.LoginRole; import com.petmatz.domain.user.constant.LoginType; import com.petmatz.domain.user.entity.KakaoRegion; @@ -18,7 +20,7 @@ @Component @RequiredArgsConstructor @Slf4j -public class KakaoUserComponent { +public class KakaoUserService { /** * 카카오 프로필 수정 어디다가 넣을지 고민중,,, diff --git a/src/main/java/com/petmatz/domain/user/component/PageComponent.java b/src/main/java/com/petmatz/domain/user/service/PageService.java similarity index 89% rename from src/main/java/com/petmatz/domain/user/component/PageComponent.java rename to src/main/java/com/petmatz/domain/user/service/PageService.java index 87b842e..add1de4 100644 --- a/src/main/java/com/petmatz/domain/user/component/PageComponent.java +++ b/src/main/java/com/petmatz/domain/user/service/PageService.java @@ -1,8 +1,9 @@ -package com.petmatz.domain.user.component; +package com.petmatz.domain.user.service; import com.petmatz.common.security.utils.JwtExtractProvider; import com.petmatz.domain.aws.AwsClient; import com.petmatz.domain.aws.vo.S3Imge; +import com.petmatz.domain.user.component.UserUtils; import com.petmatz.domain.user.entity.User; import com.petmatz.domain.user.info.EditMyProfileInfo; import com.petmatz.domain.user.repository.HeartRepository; @@ -12,7 +13,6 @@ import com.petmatz.domain.user.response.GetOtherProfileResponseDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -20,16 +20,9 @@ @Component @RequiredArgsConstructor -@Slf4j -public class PageComponent { - - /** - * 공통 부분 좀 빼야 할 것 같음 - */ +public class PageService { private final JwtExtractProvider jwtExtractProvider; - private final UserRepository userRepository; - private final HeartRepository heartRepository; private final AwsClient awsClient; private final UserUtils userUtils; diff --git a/src/main/java/com/petmatz/domain/user/service/UserService.java b/src/main/java/com/petmatz/domain/user/service/UserService.java index 694df81..a6166a7 100644 --- a/src/main/java/com/petmatz/domain/user/service/UserService.java +++ b/src/main/java/com/petmatz/domain/user/service/UserService.java @@ -8,23 +8,22 @@ import org.springframework.http.ResponseEntity; public interface UserService { - ResponseEntity emailCertification(EmailCertificationRequestDto dto); - ResponseEntity checkCertification(CheckCertificationInfo info); - ResponseEntity signUp(SignUpInfo info); - ResponseEntity signIn(SignInInfo info, HttpServletResponse response); - ResponseEntity logout(HttpServletResponse response); - ResponseEntity deleteId(DeleteIdRequestDto dto); - ResponseEntity getMypage(); - ResponseEntity getOtherMypage(Long userId); - ResponseEntity sendRepassword(SendRepasswordRequestDto dto); - ResponseEntity repassword(RepasswordInfo info); - ResponseEntity editMyProfile(EditMyProfileInfo info); - ResponseEntity hearting(HeartingRequestDto dto); - ResponseEntity getHeartedList(); - ResponseEntity updateLocation(UpdateLocationInfo info); - ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto); - ResponseEntity editKakaoProfile(EditKakaoProfileInfo of); - +// ResponseEntity emailCertification(EmailCertificationRequestDto dto); +// ResponseEntity checkCertification(CheckCertificationInfo info); +// ResponseEntity signUp(SignUpInfo info); +// ResponseEntity signIn(SignInInfo info, HttpServletResponse response); +// ResponseEntity logout(HttpServletResponse response); +// ResponseEntity deleteId(DeleteIdRequestDto dto); +// ResponseEntity getMypage(); +// ResponseEntity getOtherMypage(Long userId); +// ResponseEntity sendRepassword(SendRepasswordRequestDto dto); +// ResponseEntity repassword(RepasswordInfo info); +// ResponseEntity editMyProfile(EditMyProfileInfo info); +// ResponseEntity hearting(HeartingRequestDto dto); +// ResponseEntity getHeartedList(); +// ResponseEntity updateLocation(UpdateLocationInfo info); +// ResponseEntity updateRecommend(UpdateRecommendationRequestDto dto); +// ResponseEntity editKakaoProfile(EditKakaoProfileInfo of); String findByUserEmail(Long userId); From 435f091c0bd537070b81bf32033a4ea7c02aac56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A5=98=ED=9D=AC=EC=88=98?= Date: Wed, 15 Jan 2025 01:12:41 +0900 Subject: [PATCH 13/13] =?UTF-8?q?Refactor=20:=20=EC=A0=84=EC=97=90=20?= =?UTF-8?q?=EC=93=B0=EB=8D=98=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=EB=A1=9C=20=EC=A3=BC=EC=84=9D=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=ED=99=95=EC=9D=B8=EB=90=98=EB=A9=B4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/PastUserController.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/main/java/com/petmatz/api/user/controller/PastUserController.java diff --git a/src/main/java/com/petmatz/api/user/controller/PastUserController.java b/src/main/java/com/petmatz/api/user/controller/PastUserController.java new file mode 100644 index 0000000..f0cd8ad --- /dev/null +++ b/src/main/java/com/petmatz/api/user/controller/PastUserController.java @@ -0,0 +1,149 @@ +//package com.petmatz.api.user.controller; +// +//import com.petmatz.api.user.request.*; +//import com.petmatz.domain.user.service.AuthService; +//import com.petmatz.domain.user.response.*; +//import com.petmatz.domain.user.service.UserService; +//import com.petmatz.user.common.LogInResponseDto; +//import jakarta.servlet.http.HttpServletResponse; +//import jakarta.validation.Valid; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.*; +// +//import java.net.MalformedURLException; +// +//@Slf4j +//@RestController +//@RequiredArgsConstructor +//@RequestMapping("/api/auth") +//public class PastUserController { +// private final UserService userService; +// private final AuthService authService; +// +// @PostMapping("/email-certification") +// public ResponseEntity emailCertification(@RequestBody @Valid EmailCertificationRequestDto requestBody) { +// ResponseEntity response = userService.emailCertification(requestBody); +// log.info("[emailCertification]: { accountId: " + requestBody.getAccountId() + "}"); +// return response; +// } // 하나 했음 +// +// @PostMapping("/check-certification") +// public ResponseEntity checkCertification(@RequestBody @Valid CheckCertificationRequestDto requestBody) { +// ResponseEntity response = userService.checkCertification(CheckCertificationRequestDto.of(requestBody)); +// log.info("[checkCertification]: {accountId: " + requestBody.getAccountId() + ", certificationNumber: " + requestBody.getCertificationNumber() + "}"); +// return response; +// } // 했음 +// +// @PostMapping("/sign-up") +// public ResponseEntity signUp(@RequestBody @Valid SignUpRequestDto requestBody) throws MalformedURLException { +// SignUpResponseDto responseDto = authService.signUp(SignUpRequestDto.of(requestBody)); +// log.info("[signUp]: { accountId: " + requestBody.getAccountId() + ", password: " + requestBody.getPassword()); +// return ResponseEntity.ok(responseDto); +// } // o +// +// @PostMapping("/sign-in") +// public ResponseEntity signIn( +// @RequestBody @Valid SignInRequestDto requestBody, +// HttpServletResponse response) { +// +// ResponseEntity result = userService.signIn(SignInRequestDto.of(requestBody), response); +// log.info("[signIn]: { accountId: " + requestBody.getAccountId() + ", result: " + result.getStatusCode() + " }"); +// return result; +// } // o +// +// @PostMapping("/delete-user") +// public ResponseEntity deleteUser(@RequestBody @Valid DeleteIdRequestDto requestBody) { +// ResponseEntity response = userService.deleteId(requestBody); +// log.info("[deleteUser]:{password: " + requestBody.getPassword() + "}"); +// return response; +// } // o +// +// //---------------------------------------------------------------------------------------------------------------------------------// +// @GetMapping("/get-myprofile") +// public ResponseEntity getMypage() { +// ResponseEntity response = userService.getMypage(); +// log.info("[getMypage]"); +// return response; +// } // o +// +// @GetMapping("/get-otherprofile") +// public ResponseEntity getOtherMypage(@RequestParam @Valid Long userId) { +// ResponseEntity response = userService.getOtherMypage(userId); +// log.info("[getOtherMypage]"); +// return response; +// } // o +// +// @PostMapping("/edit-myprofile") +// public ResponseEntity editMyProfile(@RequestBody @Valid EditMyProfileRequestDto requestBody) { +// ResponseEntity response = userService.editMyProfile(EditMyProfileRequestDto.of(requestBody)); +// log.info("[editMyProfile]"); +// return response; +// } // o +// +// @PostMapping("/send-repassword") +// public ResponseEntity sendRepassword(@RequestBody @Valid SendRepasswordRequestDto requestBody) { +// ResponseEntity response = userService.sendRepassword(requestBody); +// log.info("[sendRepassword]: {accountId: " + requestBody.getAccountId() + "}"); +// return response; +// } // o +// +// @PostMapping("/repassword") +// public ResponseEntity repassword(@RequestBody @Valid RepasswordRequestDto requestBody) { +// ResponseEntity response = userService.repassword(RepasswordRequestDto.of(requestBody)); +// log.info("[repassword]: {currentPassword: " + requestBody.getCurrentPassword() + ", newPassword: " + requestBody.getNewPassword() + "}"); +// return response; +// } // o +// +// //--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// @PostMapping("/hearting") +// public ResponseEntity hearting(@RequestBody @Valid HeartingRequestDto requestBody) { +// ResponseEntity response = userService.hearting(requestBody); +// log.info("[hearting]: {heartedId: " + requestBody.getHeartedId() + "}"); +// return response; +// } // o +// +// @GetMapping("/get-heartlist") +// public ResponseEntity getHeartedList() { +// ResponseEntity response = userService.getHeartedList(); +// log.info("[getHeartedList]"); +// return response; +// } // o +// +// @PostMapping("/update-location") +// public ResponseEntity updateLocation(@RequestBody @Valid UpdateLocationRequestDto requestBody) { +// ResponseEntity response = userService.updateLocation(UpdateLocationRequestDto.of(requestBody)); +// log.info("[updateLocation]"); +// return response; +// } // o +// +// @PostMapping("/update-recommendation") +// public ResponseEntity updateRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { +// ResponseEntity response = userService.updateRecommend(requestBody); +// log.info("[updateRecommend]"); +// return response; +// } +// +// @GetMapping("/get-recommended") +// public ResponseEntity getRecommend(@RequestBody @Valid UpdateRecommendationRequestDto requestBody) { +// ResponseEntity response = userService.getRecommend(requestBody); +// log.info("[getRecommend]"); +// return response; +// } +// +// @PostMapping("/logout") +// public ResponseEntity logout(HttpServletResponse res) { +// ResponseEntity response = userService.logout(res); +// log.info("[logout]"); +// return response; +// } +// +// @PostMapping("/edit-kakaoprofile") +// public ResponseEntity editKakaoProfile(@RequestBody @Valid EditKakaoProfileRequestDto requestBody) { +// ResponseEntity response = userService.editKakaoProfile(EditKakaoProfileRequestDto.of(requestBody)); +// log.info("[editKakaoProfile]"); +// return response; +// } +//} +//