diff --git a/be/src/main/java/yeonba/be/exception/BlockException.java b/be/src/main/java/yeonba/be/exception/BlockException.java index 71609542..cd2aaee8 100644 --- a/be/src/main/java/yeonba/be/exception/BlockException.java +++ b/be/src/main/java/yeonba/be/exception/BlockException.java @@ -1,7 +1,11 @@ package yeonba.be.exception; +import lombok.AllArgsConstructor; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter +@AllArgsConstructor public enum BlockException implements BaseException { ALREADY_BLOCKED_USER( @@ -14,21 +18,4 @@ public enum BlockException implements BaseException { private final HttpStatus httpStatus; private final String reason; - - BlockException(HttpStatus httpStatus, String reason) { - this.httpStatus = httpStatus; - this.reason = reason; - } - - @Override - public HttpStatus getHttpStatus() { - - return httpStatus; - } - - @Override - public String getReason() { - - return reason; - } } diff --git a/be/src/main/java/yeonba/be/exception/ChatException.java b/be/src/main/java/yeonba/be/exception/ChatException.java index cc7a5b4c..5c5fbc31 100644 --- a/be/src/main/java/yeonba/be/exception/ChatException.java +++ b/be/src/main/java/yeonba/be/exception/ChatException.java @@ -1,7 +1,11 @@ package yeonba.be.exception; +import lombok.AllArgsConstructor; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter +@AllArgsConstructor public enum ChatException implements BaseException { NOT_YOUR_CHAT_ROOM( @@ -18,21 +22,4 @@ public enum ChatException implements BaseException { private final HttpStatus httpStatus; private final String reason; - - ChatException(HttpStatus httpStatus, String reason) { - this.httpStatus = httpStatus; - this.reason = reason; - } - - @Override - public HttpStatus getHttpStatus() { - - return httpStatus; - } - - @Override - public String getReason() { - - return reason; - } } diff --git a/be/src/main/java/yeonba/be/exception/CommonException.java b/be/src/main/java/yeonba/be/exception/CommonException.java index cef63d58..48f0a6e4 100644 --- a/be/src/main/java/yeonba/be/exception/CommonException.java +++ b/be/src/main/java/yeonba/be/exception/CommonException.java @@ -1,32 +1,21 @@ package yeonba.be.exception; +import lombok.AllArgsConstructor; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter +@AllArgsConstructor public enum CommonException implements BaseException { - BAD_REQUEST( - HttpStatus.BAD_REQUEST, - "잘못된 요청입니다."), + BAD_REQUEST( + HttpStatus.BAD_REQUEST, + "잘못된 요청입니다."), - INTERNAL_SERVER_ERROR( - HttpStatus.INTERNAL_SERVER_ERROR, - "서버 에러 관리자에게 문의 바랍니다."); + INTERNAL_SERVER_ERROR( + HttpStatus.INTERNAL_SERVER_ERROR, + "서버 에러 관리자에게 문의 바랍니다."); - private final HttpStatus httpStatus; - private final String reason; - - CommonException(HttpStatus httpStatus, String reason) { - this.httpStatus = httpStatus; - this.reason = reason; - } - - @Override - public HttpStatus getHttpStatus() { - return httpStatus; - } - - @Override - public String getReason() { - return reason; - } + private final HttpStatus httpStatus; + private final String reason; } diff --git a/be/src/main/java/yeonba/be/exception/FavoriteException.java b/be/src/main/java/yeonba/be/exception/FavoriteException.java index 85fef16e..71f55b80 100644 --- a/be/src/main/java/yeonba/be/exception/FavoriteException.java +++ b/be/src/main/java/yeonba/be/exception/FavoriteException.java @@ -1,34 +1,21 @@ package yeonba.be.exception; +import lombok.AllArgsConstructor; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter +@AllArgsConstructor public enum FavoriteException implements BaseException { - ALREADY_FAVORITE_USER( - HttpStatus.BAD_REQUEST, - "이미 즐겨찾기한 사용자입니다."), + ALREADY_FAVORITE_USER( + HttpStatus.BAD_REQUEST, + "이미 즐겨찾기한 사용자입니다."), - FAVORITE_NOT_FOUND( - HttpStatus.BAD_REQUEST, - "해당 즐겨찾기 내역이 존재하지 않습니다."); + FAVORITE_NOT_FOUND( + HttpStatus.BAD_REQUEST, + "해당 즐겨찾기 내역이 존재하지 않습니다."); - private final HttpStatus httpStatus; - private final String reason; - - FavoriteException(HttpStatus httpStatus, String reason) { - this.httpStatus = httpStatus; - this.reason = reason; - } - - @Override - public HttpStatus getHttpStatus() { - - return httpStatus; - } - - @Override - public String getReason() { - - return reason; - } + private final HttpStatus httpStatus; + private final String reason; } diff --git a/be/src/main/java/yeonba/be/exception/ReportException.java b/be/src/main/java/yeonba/be/exception/ReportException.java index 4c5e5d23..01dc728c 100644 --- a/be/src/main/java/yeonba/be/exception/ReportException.java +++ b/be/src/main/java/yeonba/be/exception/ReportException.java @@ -1,36 +1,25 @@ package yeonba.be.exception; +import lombok.AllArgsConstructor; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter +@AllArgsConstructor public enum ReportException implements BaseException { - REPORT_REASON_NOT_EXIST( - HttpStatus.BAD_REQUEST, - "신고 분류가 기타일 때는 사유가 존재해야 합니다."), + REPORT_REASON_NOT_EXIST( + HttpStatus.BAD_REQUEST, + "신고 분류가 기타일 때는 사유가 존재해야 합니다."), - REPORT_REASON_LENGTH_NOT_VALID( - HttpStatus.BAD_REQUEST, - "신고 사유는 1024자를 넘을 수 없습니다."), + REPORT_REASON_LENGTH_NOT_VALID( + HttpStatus.BAD_REQUEST, + "신고 사유는 1024자를 넘을 수 없습니다."), - CAN_NOT_REPORT_SELF( - HttpStatus.BAD_REQUEST, - "자기 자신을 신고할 수 없습니다."); + CAN_NOT_REPORT_SELF( + HttpStatus.BAD_REQUEST, + "자기 자신을 신고할 수 없습니다."); - private final HttpStatus httpStatus; - private final String reason; - - ReportException(HttpStatus httpStatus, String reason) { - this.httpStatus = httpStatus; - this.reason = reason; - } - - @Override - public HttpStatus getHttpStatus() { - return httpStatus; - } - - @Override - public String getReason() { - return reason; - } + private final HttpStatus httpStatus; + private final String reason; } diff --git a/be/src/main/java/yeonba/be/login/dto/request/UserJoinRequest.java b/be/src/main/java/yeonba/be/login/dto/request/UserJoinRequest.java index cdc48993..1163b6f9 100644 --- a/be/src/main/java/yeonba/be/login/dto/request/UserJoinRequest.java +++ b/be/src/main/java/yeonba/be/login/dto/request/UserJoinRequest.java @@ -126,8 +126,8 @@ public class UserJoinRequest { description = "사진 싱크로율", example = "80") @Min( - value = 80, - message = "사진 싱크로율이 80퍼 이상이어야 가입할 수 있습니다.") + value = 75, + message = "사진 싱크로율이 75퍼 이상이어야 가입할 수 있습니다.") @NotNull(message = "사진 싱크로율은 반드시 입력되어야 합니다.") private int photoSyncRate; diff --git a/be/src/main/java/yeonba/be/mypage/controller/MyPageController.java b/be/src/main/java/yeonba/be/mypage/controller/MyPageController.java index 7ca46829..3f0f4f91 100644 --- a/be/src/main/java/yeonba/be/mypage/controller/MyPageController.java +++ b/be/src/main/java/yeonba/be/mypage/controller/MyPageController.java @@ -5,23 +5,21 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import jakarta.validation.constraints.Size; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; import yeonba.be.mypage.dto.request.NotificationPermissionsUpdateRequest; import yeonba.be.mypage.dto.request.UserChangeInactiveStatusRequest; +import yeonba.be.mypage.dto.request.UserUpdateProfilePhotoRequest; import yeonba.be.mypage.dto.request.UserUpdateProfileRequest; import yeonba.be.mypage.dto.request.UserUpdateUnwantedAcquaintancesRequest; import yeonba.be.mypage.dto.response.BlockedUsersResponse; @@ -71,12 +69,9 @@ public ResponseEntity> getProfileDetai @PutMapping(path = "/users/profile-photos", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> updateProfilePhotos( @RequestAttribute("userId") long userId, - @Parameter(description = "업로드 할 새로운 프로필 사진들, 반드시 한 번에 2개씩 업로드") - @RequestPart("profilePhotos") @Size(min = 2, max = 2) List profilePhotos, - @Parameter(description = "프로필 사진과 싱크로율을 검증할 직접 찍은 사진") - @RequestPart("realTimePhoto") @Size(min = 1, max = 1) MultipartFile realTimePhoto) { + @Valid @ModelAttribute UserUpdateProfilePhotoRequest request) { - myPageService.updateProfilePhotos(profilePhotos, realTimePhoto, userId); + myPageService.updateProfilePhotos(userId, request); return ResponseEntity .ok() @@ -87,8 +82,8 @@ public ResponseEntity> updateProfilePhotos( @ApiResponse(responseCode = "200", description = "자신의 프로필 수정 요청 정상 처리") @PatchMapping("/users/profiles") public ResponseEntity> updateProfile( - @Valid @RequestBody UserUpdateProfileRequest request, - @RequestAttribute("userId") long userId) { + @RequestAttribute("userId") long userId, + @Valid @RequestBody UserUpdateProfileRequest request) { myPageService.updateProfile(request, userId); diff --git a/be/src/main/java/yeonba/be/mypage/dto/request/UserUpdateProfilePhotoRequest.java b/be/src/main/java/yeonba/be/mypage/dto/request/UserUpdateProfilePhotoRequest.java new file mode 100644 index 00000000..81d787a3 --- /dev/null +++ b/be/src/main/java/yeonba/be/mypage/dto/request/UserUpdateProfilePhotoRequest.java @@ -0,0 +1,30 @@ +package yeonba.be.mypage.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.web.multipart.MultipartFile; + +@Getter +@AllArgsConstructor +public class UserUpdateProfilePhotoRequest { + + @Schema( + type = "array", + description = "수정할 프로필 사진들") + @Size(min = 2, max = 2, message = "프로필 사진은 정확히 2장이어야 합니다.") + @NotNull(message = "프로필 사진은 반드시 입력되어야 합니다.") + List profilePhotos; + + @Schema( + type = "number", + description = "사진 싱크로율", + example = "85") + @Min(value = 75, message = "사진 싱크로율이 75퍼이상이어야 합니다.") + @NotNull(message = "사진 싱크로율은 반드시 입력되어야 합니다.") + private int photoSyncRate; +} diff --git a/be/src/main/java/yeonba/be/mypage/service/MyPageService.java b/be/src/main/java/yeonba/be/mypage/service/MyPageService.java index bb1f2f2b..ce9f7012 100644 --- a/be/src/main/java/yeonba/be/mypage/service/MyPageService.java +++ b/be/src/main/java/yeonba/be/mypage/service/MyPageService.java @@ -1,19 +1,13 @@ package yeonba.be.mypage.service; -import static yeonba.be.util.BoundsValidator.validateBounds; - import java.time.LocalDate; import java.time.Period; import java.util.List; +import java.util.Objects; import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; import yeonba.be.exception.BlockException; import yeonba.be.exception.GeneralException; import yeonba.be.exception.NotificationException; @@ -21,6 +15,7 @@ import yeonba.be.mypage.dto.NotificationPermissionDetail; import yeonba.be.mypage.dto.request.NotificationPermissionsUpdateRequest; import yeonba.be.mypage.dto.request.UserChangeInactiveStatusRequest; +import yeonba.be.mypage.dto.request.UserUpdateProfilePhotoRequest; import yeonba.be.mypage.dto.request.UserUpdateProfileRequest; import yeonba.be.mypage.dto.response.BlockedUserResponse; import yeonba.be.mypage.dto.response.BlockedUsersResponse; @@ -44,6 +39,8 @@ import yeonba.be.user.repository.userpreference.UserPreferenceQuery; import yeonba.be.user.repository.vocalrange.VocalRangeQuery; import yeonba.be.util.AgeValidator; +import yeonba.be.util.BoundsValidator; +import yeonba.be.util.S3Service; @Service @RequiredArgsConstructor @@ -59,10 +56,7 @@ public class MyPageService { private final BlockCommand blockCommand; - private final S3Client s3Client; - - @Value("${S3_BUCKET_NAME}") - private String bucketName; + private final S3Service s3Service; @Transactional(readOnly = true) public UserSimpleProfileResponse getSimpleProfile(long userId) { @@ -118,11 +112,11 @@ public void updateProfile(UserUpdateProfileRequest request, long userId) { // 하한, 상한 값이 모두 존재할 경우만 하한 <= 상한 검증 Integer preferredAgeLowerBound = request.getPreferredAgeLowerBound(); Integer preferredAgeUpperBound = request.getPreferredAgeUpperBound(); - validateBounds(preferredAgeLowerBound, preferredAgeUpperBound); + BoundsValidator.validateBounds(preferredAgeLowerBound, preferredAgeUpperBound); Integer preferredHeightLowerBound = request.getPreferredHeightLowerBound(); Integer preferredHeightUpperBound = request.getPreferredHeightUpperBound(); - validateBounds(preferredHeightLowerBound, preferredHeightUpperBound); + BoundsValidator.validateBounds(preferredHeightLowerBound, preferredHeightUpperBound); // 사용자 프로필 및 선호 조건 업데이트 user.updateProfile( @@ -174,14 +168,14 @@ private Area findAreaByName(List areas, String name) { .orElseThrow(() -> new GeneralException(UserException.AREA_NOT_FOUND)); } - public void updateProfilePhotos(List profilePhotos, MultipartFile realTimePhoto, - long userId) { + @Transactional + public void updateProfilePhotos(long userId, UserUpdateProfilePhotoRequest request) { User user = userQuery.findById(userId); - // TODO: AI server 연동 후 얼굴 인식 로직 추가 - // TODO: 사용자마다 정해전 경로에 파일을 업로드 하기 때문에 회원 가입 시 파일을 저장할 경로를 만들어야 함. - uploadProfilePhotos(profilePhotos, user); + // 사진 업로드 및 사진 싱크로율 업데이트 + s3Service.uploadProfilePhotos(request.getProfilePhotos(), user); + user.updatePhotoSyncRate(request.getPhotoSyncRate()); } public BlockedUsersResponse getBlockedUsers(long userId) { @@ -228,40 +222,6 @@ public void deleteUser(long userId) { user.delete(); } - /** - * 사용자마다 정해진 profile photo url에 파일을 업로드한다. - */ - private void uploadProfilePhotos(List profilePhotos, User user) { - - List fileNames = user.getProfilePhotoUrls(); - - // TODO: 회의 후 확장자 제한 로직 추가, 확장자 검증 후 업로드 시작 - // validateFileExtension(profilePhoto); - - for (int profilePhotoIdx = 0; profilePhotoIdx < profilePhotos.size(); - profilePhotoIdx++) { - - MultipartFile profilePhoto = profilePhotos.get(profilePhotoIdx); - - PutObjectRequest putObjectRequest = PutObjectRequest.builder() - .bucket(bucketName) - .key(fileNames.get(profilePhotoIdx)) - .contentDisposition("inline") - .contentType(profilePhoto.getContentType()) - .build(); - - try { - s3Client.putObject(putObjectRequest, - RequestBody.fromInputStream(profilePhoto.getInputStream(), - profilePhoto.getSize())); - - } catch (Exception e) { - throw new IllegalStateException( - "Failed to upload file: " + profilePhoto.getOriginalFilename(), e); - } - } - } - @Transactional(readOnly = true) public NotificationPermissionsResponse getNotificationPermissions(long userId) { diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java index 6dca8fb3..cdb09fe5 100644 --- a/be/src/main/java/yeonba/be/user/entity/User.java +++ b/be/src/main/java/yeonba/be/user/entity/User.java @@ -265,4 +265,9 @@ public void updateProfile( this.animal = animal; this.area = area; } + + public void updatePhotoSyncRate(int photoSyncRate) { + + this.photoSyncRate = photoSyncRate; + } }