From cd05796e5bdc539282dd3ceb0103a1d31709bea0 Mon Sep 17 00:00:00 2001 From: Big-Cir97 Date: Tue, 26 Dec 2023 21:20:49 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=82=AC=EC=A7=84=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EC=84=9C=EB=B9=84=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/port/in/SaveFileUseCase.java | 15 ----- .../port/in/dto/MultiFileResponseDto.java | 5 -- .../port/in/dto/SaveFileResult.java | 7 --- .../application/service/SaveFileService.java | 62 ------------------ .../in/http/SetUserProfileController.java | 31 --------- .../in/http/UserSupportController.java | 27 +++----- .../port/in/SetUserProfileUseCase.java | 10 --- .../port/in/UpdateProfilePicUseCase.java | 11 ++++ .../port/in/dto/UpdateProfilePicResult.java | 10 +++ .../service/SetUserProfileService.java | 44 ------------- .../service/SupportUserService.java | 13 +--- .../service/UpdateProfilePicService.java | 41 ++++++++++++ .../in/http/UserSupportControllerTest.java | 63 +++++++++++++------ 13 files changed, 119 insertions(+), 220 deletions(-) delete mode 100644 perfume-api/src/main/java/io/perfume/api/file/application/port/in/SaveFileUseCase.java delete mode 100644 perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/MultiFileResponseDto.java delete mode 100644 perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/SaveFileResult.java delete mode 100644 perfume-api/src/main/java/io/perfume/api/file/application/service/SaveFileService.java delete mode 100644 perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/SetUserProfileController.java delete mode 100644 perfume-api/src/main/java/io/perfume/api/user/application/port/in/SetUserProfileUseCase.java create mode 100644 perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java create mode 100644 perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java delete mode 100644 perfume-api/src/main/java/io/perfume/api/user/application/service/SetUserProfileService.java create mode 100644 perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java diff --git a/perfume-api/src/main/java/io/perfume/api/file/application/port/in/SaveFileUseCase.java b/perfume-api/src/main/java/io/perfume/api/file/application/port/in/SaveFileUseCase.java deleted file mode 100644 index 383c672c..00000000 --- a/perfume-api/src/main/java/io/perfume/api/file/application/port/in/SaveFileUseCase.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.perfume.api.file.application.port.in; - -import io.perfume.api.file.application.port.in.dto.MultiFileResponseDto; -import io.perfume.api.file.application.port.in.dto.SaveFileResult; -import java.time.LocalDateTime; -import java.util.List; -import org.springframework.security.core.userdetails.User; -import org.springframework.web.multipart.MultipartFile; - -public interface SaveFileUseCase { - - SaveFileResult singleFileUpload(Long userId, MultipartFile file, LocalDateTime now); - - MultiFileResponseDto multiFileUpload(User user, List files, LocalDateTime now); -} diff --git a/perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/MultiFileResponseDto.java b/perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/MultiFileResponseDto.java deleted file mode 100644 index 2ceb40a0..00000000 --- a/perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/MultiFileResponseDto.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.perfume.api.file.application.port.in.dto; - -import java.util.List; - -public record MultiFileResponseDto(List saveFiles) {} diff --git a/perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/SaveFileResult.java b/perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/SaveFileResult.java deleted file mode 100644 index 8acc1525..00000000 --- a/perfume-api/src/main/java/io/perfume/api/file/application/port/in/dto/SaveFileResult.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.perfume.api.file.application.port.in.dto; - -import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; - -public record SaveFileResult( - @NotNull String url, @NotNull Long userId, @NotNull Long fileId, LocalDateTime now) {} diff --git a/perfume-api/src/main/java/io/perfume/api/file/application/service/SaveFileService.java b/perfume-api/src/main/java/io/perfume/api/file/application/service/SaveFileService.java deleted file mode 100644 index 25975299..00000000 --- a/perfume-api/src/main/java/io/perfume/api/file/application/service/SaveFileService.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.perfume.api.file.application.service; - -import io.perfume.api.file.application.exception.SaveFileNotFoundException; -import io.perfume.api.file.application.port.in.SaveFileUseCase; -import io.perfume.api.file.application.port.in.dto.MultiFileResponseDto; -import io.perfume.api.file.application.port.in.dto.SaveFileResult; -import io.perfume.api.file.application.port.out.FileRepository; -import io.perfume.api.file.domain.File; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import org.springframework.security.core.userdetails.User; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@Service -@Transactional -public class SaveFileService implements SaveFileUseCase { - - private final FileRepository fileRepository; - - private final String cdnUrl = ""; - - public SaveFileService(FileRepository fileRepository) { - this.fileRepository = fileRepository; - } - - @Override - public SaveFileResult singleFileUpload(Long userId, MultipartFile file, LocalDateTime now) { - String URL = cdnUrl + file.getOriginalFilename(); - if (file != null && !file.isEmpty()) { - File saveFile = fileRepository.save(File.createFile(URL, userId, now)); - return new SaveFileResult(saveFile.getUrl(), userId, saveFile.getId(), now); - } else { - throw new SaveFileNotFoundException(); - } - } - - @Override - public MultiFileResponseDto multiFileUpload( - User user, List files, LocalDateTime now) { - List saveFiles = new ArrayList<>(); - if (files != null && !files.isEmpty()) { - for (MultipartFile file : files) { - String URL = cdnUrl + file.getOriginalFilename(); - saveFiles.add(File.createFile(URL, Long.parseLong(user.getUsername()), now)); - } - return toDto(fileRepository.saveAll(saveFiles), now); - } else { - throw new SaveFileNotFoundException(); - } - } - - private MultiFileResponseDto toDto(List saveFiles, LocalDateTime now) { - List results = new ArrayList<>(); - for (File file : saveFiles) { - results.add(new SaveFileResult(file.getUrl(), file.getUserId(), file.getId(), now)); - } - return new MultiFileResponseDto(results); - } -} diff --git a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/SetUserProfileController.java b/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/SetUserProfileController.java deleted file mode 100644 index a0f93bac..00000000 --- a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/SetUserProfileController.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.perfume.api.user.adapter.in.http; - -import io.perfume.api.user.application.port.in.SetUserProfileUseCase; -import java.time.LocalDateTime; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.User; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -@RestController -@RequestMapping("/v1/user") -public class SetUserProfileController { - - private final SetUserProfileUseCase setUserProfileUseCase; - - public SetUserProfileController(SetUserProfileUseCase setUserProfileUseCase) { - this.setUserProfileUseCase = setUserProfileUseCase; - } - - @PutMapping("/test-setPicture-endpoint") - public ResponseEntity setProfilePicture( - @AuthenticationPrincipal User user, @RequestPart MultipartFile image) { - LocalDateTime now = LocalDateTime.now(); - setUserProfileUseCase.setUserProfilePicture(user.getUsername(), image, now); - return ResponseEntity.ok().build(); - } -} diff --git a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java b/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java index 9499de23..e3049c32 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java +++ b/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java @@ -1,17 +1,9 @@ package io.perfume.api.user.adapter.in.http; import io.perfume.api.file.application.port.in.FindFileUseCase; -import io.perfume.api.user.adapter.in.http.dto.LeaveUserDto; -import io.perfume.api.user.adapter.in.http.dto.UpdateEmailRequestDto; -import io.perfume.api.user.adapter.in.http.dto.UpdatePasswordRequestDto; -import io.perfume.api.user.adapter.in.http.dto.UpdateProfileRequestDto; -import io.perfume.api.user.adapter.in.http.dto.UserProfileDto; +import io.perfume.api.user.adapter.in.http.dto.*; import io.perfume.api.user.adapter.in.http.exception.UserNotAuthenticatedException; -import io.perfume.api.user.application.port.in.FindEncryptedUsernameUseCase; -import io.perfume.api.user.application.port.in.FindUserUseCase; -import io.perfume.api.user.application.port.in.LeaveUserUseCase; -import io.perfume.api.user.application.port.in.SendResetPasswordMailUseCase; -import io.perfume.api.user.application.port.in.UpdateAccountUseCase; +import io.perfume.api.user.application.port.in.*; import io.perfume.api.user.application.port.in.dto.UpdateEmailCommand; import io.perfume.api.user.application.port.in.dto.UpdatePasswordCommand; import io.perfume.api.user.application.port.in.dto.UpdateProfileCommand; @@ -21,16 +13,14 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.User; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.time.LocalDateTime; @RestController @RequiredArgsConstructor @@ -42,6 +32,7 @@ public class UserSupportController { private final FindFileUseCase findFileUseCase; private final SendResetPasswordMailUseCase resetPasswordUserCase; private final UpdateAccountUseCase updateAccountUseCase; + private final UpdateProfilePicUseCase updateProfilePicUseCase; private final LeaveUserUseCase leaveUserUseCase; diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/SetUserProfileUseCase.java b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/SetUserProfileUseCase.java deleted file mode 100644 index 02ca00dc..00000000 --- a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/SetUserProfileUseCase.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.perfume.api.user.application.port.in; - -import java.time.LocalDateTime; -import org.springframework.web.multipart.MultipartFile; - -// 독립적인 기능이라면 usecase를 별도로 분리? -public interface SetUserProfileUseCase { - - void setUserProfilePicture(String userId, MultipartFile image, LocalDateTime now); -} diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java new file mode 100644 index 00000000..983b1a76 --- /dev/null +++ b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java @@ -0,0 +1,11 @@ +package io.perfume.api.user.application.port.in; + +import java.time.LocalDateTime; + +import io.perfume.api.user.application.port.in.dto.UpdateProfilePicResult; +import org.springframework.web.multipart.MultipartFile; + +public interface UpdateProfilePicUseCase { + + UpdateProfilePicResult update(Long userId, byte[] imageFileContent, LocalDateTime now); +} diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java new file mode 100644 index 00000000..17c05233 --- /dev/null +++ b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java @@ -0,0 +1,10 @@ +package io.perfume.api.user.application.port.in.dto; + +import io.perfume.api.file.application.port.in.dto.FileResult; + +public record UpdateProfilePicResult(Long userId, Long fileId, String url) { + + public static UpdateProfilePicResult from(Long userId, FileResult fileResult) { + return new UpdateProfilePicResult(userId, fileResult.id(), fileResult.url()); + } +} diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/service/SetUserProfileService.java b/perfume-api/src/main/java/io/perfume/api/user/application/service/SetUserProfileService.java deleted file mode 100644 index b575fa43..00000000 --- a/perfume-api/src/main/java/io/perfume/api/user/application/service/SetUserProfileService.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.perfume.api.user.application.service; - -import io.perfume.api.file.application.port.in.dto.SaveFileResult; -import io.perfume.api.file.application.service.SaveFileService; -import io.perfume.api.user.application.exception.NotFoundUserException; -import io.perfume.api.user.application.port.in.SetUserProfileUseCase; -import io.perfume.api.user.application.port.out.UserQueryRepository; -import io.perfume.api.user.application.port.out.UserRepository; -import io.perfume.api.user.domain.User; -import java.time.LocalDateTime; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@Service -@Transactional -public class SetUserProfileService implements SetUserProfileUseCase { - - private final UserQueryRepository userQueryRepository; - private final UserRepository userRepository; - private final SaveFileService fileUploadService; - - public SetUserProfileService( - UserQueryRepository userQueryRepository, - UserRepository userRepository, - SaveFileService fileUploadService) { - this.userQueryRepository = userQueryRepository; - this.userRepository = userRepository; - this.fileUploadService = fileUploadService; - } - - @Override - public void setUserProfilePicture(String userId, MultipartFile image, LocalDateTime now) { - if (userId == null) { - new NotFoundUserException(); - } else { - SaveFileResult saveFileResult = - fileUploadService.singleFileUpload(Long.parseLong(userId), image, now); - User user = userQueryRepository.loadUser(Long.parseLong(userId)).get(); - user.updateThumbnailId(saveFileResult.fileId()); - userRepository.save(user); - } - } -} diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java b/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java index 65b056b4..85d5b89c 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java +++ b/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java @@ -3,6 +3,7 @@ import encryptor.OneWayEncryptor; import io.perfume.api.user.application.exception.FailedToLeaveException; import io.perfume.api.user.application.exception.NotFoundUserException; +import io.perfume.api.user.application.exception.UserNotFoundException; import io.perfume.api.user.application.port.in.FindEncryptedUsernameUseCase; import io.perfume.api.user.application.port.in.LeaveUserUseCase; import io.perfume.api.user.application.port.in.SendResetPasswordMailUseCase; @@ -59,24 +60,16 @@ public String findEncryptedUsername(String email) { @Override public void leave(Long userId) { - try { - - User user = userQueryRepository.loadUser(userId).orElseThrow(NotFoundUserException::new); - + User user = userQueryRepository.loadUser(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); LocalDateTime now = LocalDateTime.now(); - user.softDelete(now); - userRepository.save(user); - } catch (NotFoundUserException e) { - FailedToLeaveException failedLeaveException = new FailedToLeaveException("회원탈퇴 실패", "user id : " + userId + ", 회원탈퇴 실패"); - failedLeaveException.initCause(e); - throw failedLeaveException; } } diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java b/perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java new file mode 100644 index 00000000..6fc7c572 --- /dev/null +++ b/perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java @@ -0,0 +1,41 @@ +package io.perfume.api.user.application.service; + +import io.perfume.api.file.application.port.in.dto.FileResult; +import io.perfume.api.file.application.service.FileService; +import io.perfume.api.user.application.exception.UserNotFoundException; +import io.perfume.api.user.application.port.in.UpdateProfilePicUseCase; +import io.perfume.api.user.application.port.in.dto.UpdateProfilePicResult; +import io.perfume.api.user.application.port.out.UserQueryRepository; +import io.perfume.api.user.application.port.out.UserRepository; +import io.perfume.api.user.domain.User; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@Transactional +public class UpdateProfilePicService implements UpdateProfilePicUseCase { + + private final UserQueryRepository userQueryRepository; + private final UserRepository userRepository; + private final FileService fileUploadService; + + public UpdateProfilePicService( + UserQueryRepository userQueryRepository, + UserRepository userRepository, FileService fileUploadService) { + this.userQueryRepository = userQueryRepository; + this.userRepository = userRepository; + this.fileUploadService = fileUploadService; + } + + @Override + @Transactional + public UpdateProfilePicResult update(Long userId, byte[] imageFileContent, LocalDateTime now) { + FileResult fileResult = fileUploadService.uploadFile(imageFileContent, userId, now); + User user = userQueryRepository.findUserById(userId).orElseThrow(() -> new UserNotFoundException(userId)); + user.updateThumbnailId(fileResult.id()); + userRepository.save(user); + return UpdateProfilePicResult.from(userId, fileResult); + } +} diff --git a/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java b/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java index 13ea52a7..1cb0c68d 100644 --- a/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java +++ b/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java @@ -1,28 +1,11 @@ package io.perfume.api.user.adapter.in.http; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doNothing; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.fasterxml.jackson.databind.ObjectMapper; import io.perfume.api.user.adapter.in.http.dto.UpdateEmailRequestDto; import io.perfume.api.user.adapter.in.http.dto.UpdatePasswordRequestDto; import io.perfume.api.user.adapter.in.http.dto.UpdateProfileRequestDto; -import io.perfume.api.user.application.port.in.FindEncryptedUsernameUseCase; -import io.perfume.api.user.application.port.in.FindUserUseCase; -import io.perfume.api.user.application.port.in.LeaveUserUseCase; -import io.perfume.api.user.application.port.in.SendResetPasswordMailUseCase; -import io.perfume.api.user.application.port.in.UpdateAccountUseCase; +import io.perfume.api.user.application.port.in.*; import io.perfume.api.user.application.port.in.dto.UserProfileResult; -import java.time.LocalDate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -32,6 +15,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; @@ -43,6 +27,21 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; +import java.time.LocalDate; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.partWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParts; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) @Transactional @SpringBootTest @@ -54,6 +53,7 @@ class UserSupportControllerTest { @MockBean private UpdateAccountUseCase updateAccountUseCase; @MockBean private FindEncryptedUsernameUseCase findEncryptedUsernameUseCase; @MockBean private SendResetPasswordMailUseCase resetPasswordUserCase; + @MockBean private UpdateProfilePicUseCase updateProfilePicUseCase; @BeforeEach void setUp( @@ -208,6 +208,33 @@ void updateProfile() throws Exception { .description("성별 (male | female | other)만 입력 가능")))); } + @Test + @DisplayName("유저의 프로필 사진을 업데이트한다.") + @WithMockUser(username = "1", roles = "USER") + void updateProfilePic() throws Exception { + // given + final MockMultipartFile profilePic = + new MockMultipartFile("file", "test.png", "text/plain", "file".getBytes()); + + // when & then + mockMvc + .perform( + multipart("/v1/user/profile-pic") + .file(profilePic) + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + .contentType(MediaType.MULTIPART_FORM_DATA) + .with(request -> { + request.setMethod("PATCH"); + return request; + })) + .andExpect(status().isOk()) + .andDo( + document( + "update-user-profile-pic", + requestParts(partWithName("file").description("업로드할 프로필 사진")))); + } + @Test @DisplayName("로그인 상태가 아니라면 유저의 프로필을 업데이트할 수 없다.") void failToUpdateProfile() throws Exception { From 95bc0cabc7ad9a9679278db7fde5504095e97911 Mon Sep 17 00:00:00 2001 From: Big-Cir97 Date: Tue, 26 Dec 2023 21:21:21 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=88=98=EC=A0=95=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/http/UserSupportController.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java b/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java index e3049c32..bf897045 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java +++ b/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java @@ -107,6 +107,27 @@ public void updateProfileByUser( updateAccountUseCase.updateUserProfile(updateProfileCommand); } + @PatchMapping("/user/profile-pic") + @PreAuthorize("isAuthenticated()") + public void updateProfilePic(@AuthenticationPrincipal User user, final MultipartFile file) { + var now = LocalDateTime.now(); + checkAuthenticatedUser(user); + long userId = parseUserId(user); + updateProfilePicUseCase.update(userId, getFileContent(file), now); + } + + private long parseUserId(User user) { + return Long.parseLong(user.getUsername()); + } + + private byte[] getFileContent(MultipartFile file) { + try { + return file.getBytes(); + } catch (IOException e) { + return null; + } + } + private void checkAuthenticatedUser(User user) { if (user == null || user.getUsername() == null) { throw new UserNotAuthenticatedException(); From 5aebdb3cc57b1ad580de8b0e6a17e227aac763d2 Mon Sep 17 00:00:00 2001 From: Big-Cir97 Date: Tue, 26 Dec 2023 21:30:18 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=84=A4=EC=A0=95=20restdocs=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- perfume-api/src/docs/asciidoc/index.adoc | 7 +++ .../in/http/UserSupportController.java | 5 +- .../port/in/UpdateProfilePicUseCase.java | 4 +- .../port/in/dto/UpdateProfilePicResult.java | 6 +- .../service/SupportUserService.java | 4 +- .../service/UpdateProfilePicService.java | 13 ++-- .../in/http/UserSupportControllerTest.java | 62 +++++++++---------- 7 files changed, 54 insertions(+), 47 deletions(-) diff --git a/perfume-api/src/docs/asciidoc/index.adoc b/perfume-api/src/docs/asciidoc/index.adoc index 09de5704..ebc0dcf0 100644 --- a/perfume-api/src/docs/asciidoc/index.adoc +++ b/perfume-api/src/docs/asciidoc/index.adoc @@ -87,6 +87,13 @@ include::{snippets}/update-user-profile/http-response.adoc[] === 실패 응답 include::{snippets}/update-user-profile-failed/http-response.adoc[] +== 프로필 사진 수정 +=== 요청 +include::{snippets}/update-user-profile-pic/http-request.adoc[] + +=== 응답 +include::{snippets}/update-user-profile-pic/http-response.adoc[] + == 로그인 중인 회원 탈퇴 === 요청 include::{snippets}/leave-user/http-request.adoc[] diff --git a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java b/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java index bf897045..fe78cf9d 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java +++ b/perfume-api/src/main/java/io/perfume/api/user/adapter/in/http/UserSupportController.java @@ -10,6 +10,8 @@ import io.perfume.api.user.application.port.in.dto.UserProfileResult; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import java.io.IOException; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -19,9 +21,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.time.LocalDateTime; - @RestController @RequiredArgsConstructor @RequestMapping("/v1") diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java index 983b1a76..7380ebc2 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java +++ b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/UpdateProfilePicUseCase.java @@ -1,9 +1,7 @@ package io.perfume.api.user.application.port.in; -import java.time.LocalDateTime; - import io.perfume.api.user.application.port.in.dto.UpdateProfilePicResult; -import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDateTime; public interface UpdateProfilePicUseCase { diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java index 17c05233..5695ecdc 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java +++ b/perfume-api/src/main/java/io/perfume/api/user/application/port/in/dto/UpdateProfilePicResult.java @@ -4,7 +4,7 @@ public record UpdateProfilePicResult(Long userId, Long fileId, String url) { - public static UpdateProfilePicResult from(Long userId, FileResult fileResult) { - return new UpdateProfilePicResult(userId, fileResult.id(), fileResult.url()); - } + public static UpdateProfilePicResult from(Long userId, FileResult fileResult) { + return new UpdateProfilePicResult(userId, fileResult.id(), fileResult.url()); + } } diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java b/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java index 85d5b89c..bf456702 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java +++ b/perfume-api/src/main/java/io/perfume/api/user/application/service/SupportUserService.java @@ -61,8 +61,8 @@ public String findEncryptedUsername(String email) { @Override public void leave(Long userId) { try { - User user = userQueryRepository.loadUser(userId) - .orElseThrow(() -> new UserNotFoundException(userId)); + User user = + userQueryRepository.loadUser(userId).orElseThrow(() -> new UserNotFoundException(userId)); LocalDateTime now = LocalDateTime.now(); user.softDelete(now); userRepository.save(user); diff --git a/perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java b/perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java index 6fc7c572..5ce80925 100644 --- a/perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java +++ b/perfume-api/src/main/java/io/perfume/api/user/application/service/UpdateProfilePicService.java @@ -8,11 +8,10 @@ import io.perfume.api.user.application.port.out.UserQueryRepository; import io.perfume.api.user.application.port.out.UserRepository; import io.perfume.api.user.domain.User; +import java.time.LocalDateTime; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - @Service @Transactional public class UpdateProfilePicService implements UpdateProfilePicUseCase { @@ -22,8 +21,9 @@ public class UpdateProfilePicService implements UpdateProfilePicUseCase { private final FileService fileUploadService; public UpdateProfilePicService( - UserQueryRepository userQueryRepository, - UserRepository userRepository, FileService fileUploadService) { + UserQueryRepository userQueryRepository, + UserRepository userRepository, + FileService fileUploadService) { this.userQueryRepository = userQueryRepository; this.userRepository = userRepository; this.fileUploadService = fileUploadService; @@ -33,7 +33,10 @@ public UpdateProfilePicService( @Transactional public UpdateProfilePicResult update(Long userId, byte[] imageFileContent, LocalDateTime now) { FileResult fileResult = fileUploadService.uploadFile(imageFileContent, userId, now); - User user = userQueryRepository.findUserById(userId).orElseThrow(() -> new UserNotFoundException(userId)); + User user = + userQueryRepository + .findUserById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); user.updateThumbnailId(fileResult.id()); userRepository.save(user); return UpdateProfilePicResult.from(userId, fileResult); diff --git a/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java b/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java index 1cb0c68d..420e8904 100644 --- a/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java +++ b/perfume-api/src/test/java/io/perfume/api/user/adapter/in/http/UserSupportControllerTest.java @@ -1,11 +1,25 @@ package io.perfume.api.user.adapter.in.http; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.partWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParts; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import com.fasterxml.jackson.databind.ObjectMapper; import io.perfume.api.user.adapter.in.http.dto.UpdateEmailRequestDto; import io.perfume.api.user.adapter.in.http.dto.UpdatePasswordRequestDto; import io.perfume.api.user.adapter.in.http.dto.UpdateProfileRequestDto; import io.perfume.api.user.application.port.in.*; import io.perfume.api.user.application.port.in.dto.UserProfileResult; +import java.time.LocalDate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -27,21 +41,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; -import java.time.LocalDate; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doNothing; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart; -import static org.springframework.restdocs.payload.PayloadDocumentation.*; -import static org.springframework.restdocs.request.RequestDocumentation.partWithName; -import static org.springframework.restdocs.request.RequestDocumentation.requestParts; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) @Transactional @SpringBootTest @@ -214,25 +213,26 @@ void updateProfile() throws Exception { void updateProfilePic() throws Exception { // given final MockMultipartFile profilePic = - new MockMultipartFile("file", "test.png", "text/plain", "file".getBytes()); + new MockMultipartFile("file", "test.txt", "text/plain", "file".getBytes()); // when & then mockMvc - .perform( - multipart("/v1/user/profile-pic") - .file(profilePic) - .accept(MediaType.APPLICATION_JSON) - .characterEncoding("UTF-8") - .contentType(MediaType.MULTIPART_FORM_DATA) - .with(request -> { - request.setMethod("PATCH"); - return request; - })) - .andExpect(status().isOk()) - .andDo( - document( - "update-user-profile-pic", - requestParts(partWithName("file").description("업로드할 프로필 사진")))); + .perform( + multipart("/v1/user/profile-pic") + .file(profilePic) + .accept(MediaType.APPLICATION_JSON) + .characterEncoding("UTF-8") + .contentType(MediaType.MULTIPART_FORM_DATA) + .with( + request -> { + request.setMethod("PATCH"); + return request; + })) + .andExpect(status().isOk()) + .andDo( + document( + "update-user-profile-pic", + requestParts(partWithName("file").description("업로드할 프로필 사진")))); } @Test