From 0bc38be37e10d7bf2fcee89898994ea033a5c556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 18:31:19 +0900 Subject: [PATCH 1/8] =?UTF-8?q?refactor(VolunteerProfile):=20=EB=B4=89?= =?UTF-8?q?=EC=82=AC=EC=9E=90=20=ED=94=84=EB=A1=9C=ED=95=84=EC=9D=84=20?= =?UTF-8?q?=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...a => VolunteerProfileQueryController.java} | 10 +++---- ....java => VolunteerProfileResponseDto.java} | 26 +++++++++---------- .../service/VolunteerQueryService.java | 14 +++++----- .../usecase/VolunteerQueryUseCase.java | 8 +++--- .../service/VolunteerQueryServiceTest.java | 8 +++--- 5 files changed, 33 insertions(+), 33 deletions(-) rename src/main/java/com/somemore/volunteer/controller/{VolunteerQueryController.java => VolunteerProfileQueryController.java} (87%) rename src/main/java/com/somemore/volunteer/dto/response/{VolunteerResponseDto.java => VolunteerProfileResponseDto.java} (80%) diff --git a/src/main/java/com/somemore/volunteer/controller/VolunteerQueryController.java b/src/main/java/com/somemore/volunteer/controller/VolunteerProfileQueryController.java similarity index 87% rename from src/main/java/com/somemore/volunteer/controller/VolunteerQueryController.java rename to src/main/java/com/somemore/volunteer/controller/VolunteerProfileQueryController.java index 3f9a1043a..7ff1459c8 100644 --- a/src/main/java/com/somemore/volunteer/controller/VolunteerQueryController.java +++ b/src/main/java/com/somemore/volunteer/controller/VolunteerProfileQueryController.java @@ -1,7 +1,7 @@ package com.somemore.volunteer.controller; import com.somemore.global.common.response.ApiResponse; -import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.dto.response.VolunteerProfileResponseDto; import com.somemore.volunteer.usecase.VolunteerQueryUseCase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -21,14 +21,14 @@ @RequiredArgsConstructor @RequestMapping("/api/profile") @Tag(name = "GET Volunteer", description = "봉사자 조회") -public class VolunteerQueryController { +public class VolunteerProfileQueryController { private final VolunteerQueryUseCase volunteerQueryUseCase; @Operation(summary = "본인 상세 프로필 조회", description = "현재 로그인된 사용자의 상세 프로필을 조회합니다.") @Secured("ROLE_VOLUNTEER") @GetMapping("/me") - public ApiResponse getMyProfile( + public ApiResponse getMyProfile( @AuthenticationPrincipal String volunteerId) { return ApiResponse.ok( @@ -39,7 +39,7 @@ public ApiResponse getMyProfile( @GetMapping("/{volunteerId}") @Operation(summary = "타인 프로필 조회", description = "특정 봉사자의 프로필을 조회합니다. 상세 정보는 포함되지 않습니다.") - public ApiResponse getVolunteerProfile( + public ApiResponse getVolunteerProfile( @PathVariable UUID volunteerId) { return ApiResponse.ok( @@ -52,7 +52,7 @@ public ApiResponse getVolunteerProfile( @GetMapping("/{volunteerId}/detailed") @Secured("ROLE_CENTER") @Operation(summary = "지원자 상세 프로필 조회", description = "기관이 작성한 모집 글에 지원한 봉사자의 상세 프로필을 조회합니다.") - public ApiResponse getVolunteerDetailedProfile( + public ApiResponse getVolunteerDetailedProfile( @PathVariable UUID volunteerId, @AuthenticationPrincipal String centerId) { diff --git a/src/main/java/com/somemore/volunteer/dto/response/VolunteerResponseDto.java b/src/main/java/com/somemore/volunteer/dto/response/VolunteerProfileResponseDto.java similarity index 80% rename from src/main/java/com/somemore/volunteer/dto/response/VolunteerResponseDto.java rename to src/main/java/com/somemore/volunteer/dto/response/VolunteerProfileResponseDto.java index 9884804b9..2ed915645 100644 --- a/src/main/java/com/somemore/volunteer/dto/response/VolunteerResponseDto.java +++ b/src/main/java/com/somemore/volunteer/dto/response/VolunteerProfileResponseDto.java @@ -7,8 +7,8 @@ import io.swagger.v3.oas.annotations.media.Schema; @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -@Schema(description = "봉사자 응답 DTO") -public record VolunteerResponseDto( +@Schema(description = "봉사자 프로필 응답 DTO") +public record VolunteerProfileResponseDto( @Schema(description = "봉사자 ID", example = "123e4567-e89b-12d3-a456-426614174000") String volunteerId, @@ -30,15 +30,15 @@ public record VolunteerResponseDto( @Schema(description = "총 봉사 횟수", example = "20") Integer totalVolunteerCount, - @Schema(description = "봉사자 상세 정보", implementation = VolunteerDetailResponseDto.class) - VolunteerDetailResponseDto volunteerDetailResponseDto + @Schema(description = "봉사자 상세 정보", implementation = VolunteerDetailProfileResponseDto.class) + VolunteerDetailProfileResponseDto volunteerDetailProfileResponseDto ) { - public static VolunteerResponseDto from( + public static VolunteerProfileResponseDto from( Volunteer volunteer, VolunteerDetail volunteerDetail ) { - return new VolunteerResponseDto( + return new VolunteerProfileResponseDto( volunteer.getId().toString(), volunteer.getNickname(), volunteer.getImgUrl(), @@ -46,14 +46,14 @@ public static VolunteerResponseDto from( volunteer.getTier().name(), volunteer.getTotalVolunteerHours(), volunteer.getTotalVolunteerCount(), - VolunteerDetailResponseDto.from(volunteerDetail) + VolunteerDetailProfileResponseDto.from(volunteerDetail) ); } - public static VolunteerResponseDto from( + public static VolunteerProfileResponseDto from( Volunteer volunteer ) { - return new VolunteerResponseDto( + return new VolunteerProfileResponseDto( volunteer.getId().toString(), volunteer.getNickname(), volunteer.getImgUrl(), @@ -66,8 +66,8 @@ public static VolunteerResponseDto from( } @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) - @Schema(description = "봉사자 상세 응답 DTO") - private record VolunteerDetailResponseDto( + @Schema(description = "봉사자 상세 프로필 응답 DTO") + private record VolunteerDetailProfileResponseDto( @Schema(description = "이름", example = "홍길동") String name, @@ -83,10 +83,10 @@ private record VolunteerDetailResponseDto( @Schema(description = "연락처", example = "010-1234-5678") String contactNumber ) { - public static VolunteerDetailResponseDto from( + public static VolunteerDetailProfileResponseDto from( VolunteerDetail volunteerDetail ) { - return new VolunteerDetailResponseDto( + return new VolunteerDetailProfileResponseDto( volunteerDetail.getName(), volunteerDetail.getEmail(), volunteerDetail.getGender().name(), diff --git a/src/main/java/com/somemore/volunteer/service/VolunteerQueryService.java b/src/main/java/com/somemore/volunteer/service/VolunteerQueryService.java index 5e69dddbc..4e57e2bef 100644 --- a/src/main/java/com/somemore/volunteer/service/VolunteerQueryService.java +++ b/src/main/java/com/somemore/volunteer/service/VolunteerQueryService.java @@ -4,7 +4,7 @@ import com.somemore.global.exception.BadRequestException; import com.somemore.volunteer.domain.Volunteer; import com.somemore.volunteer.domain.VolunteerDetail; -import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.dto.response.VolunteerProfileResponseDto; import com.somemore.volunteer.repository.VolunteerDetailRepository; import com.somemore.volunteer.repository.VolunteerRepository; import com.somemore.volunteer.usecase.VolunteerQueryUseCase; @@ -28,27 +28,27 @@ public class VolunteerQueryService implements VolunteerQueryUseCase { private final VolunteerDetailAccessValidator volunteerDetailAccessValidator; @Override - public VolunteerResponseDto getMyProfile(UUID volunteerId) { + public VolunteerProfileResponseDto getMyProfile(UUID volunteerId) { - return VolunteerResponseDto.from( + return VolunteerProfileResponseDto.from( findVolunteer(volunteerId), findVolunteerDetail(volunteerId) ); } @Override - public VolunteerResponseDto getVolunteerProfile(UUID volunteerId) { + public VolunteerProfileResponseDto getVolunteerProfile(UUID volunteerId) { - return VolunteerResponseDto.from( + return VolunteerProfileResponseDto.from( findVolunteer(volunteerId) ); } @Override - public VolunteerResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId) { + public VolunteerProfileResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId) { volunteerDetailAccessValidator.validateByCenterId(centerId, volunteerId); - return VolunteerResponseDto.from( + return VolunteerProfileResponseDto.from( findVolunteer(volunteerId), findVolunteerDetail(volunteerId) ); diff --git a/src/main/java/com/somemore/volunteer/usecase/VolunteerQueryUseCase.java b/src/main/java/com/somemore/volunteer/usecase/VolunteerQueryUseCase.java index e41b69be6..18b00a044 100644 --- a/src/main/java/com/somemore/volunteer/usecase/VolunteerQueryUseCase.java +++ b/src/main/java/com/somemore/volunteer/usecase/VolunteerQueryUseCase.java @@ -1,16 +1,16 @@ package com.somemore.volunteer.usecase; -import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.dto.response.VolunteerProfileResponseDto; import java.util.UUID; public interface VolunteerQueryUseCase { - VolunteerResponseDto getMyProfile(UUID volunteerId); + VolunteerProfileResponseDto getMyProfile(UUID volunteerId); - VolunteerResponseDto getVolunteerProfile(UUID volunteerId); + VolunteerProfileResponseDto getVolunteerProfile(UUID volunteerId); - VolunteerResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId); + VolunteerProfileResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId); UUID getVolunteerIdByOAuthId(String oAuthId); diff --git a/src/test/java/com/somemore/volunteer/service/VolunteerQueryServiceTest.java b/src/test/java/com/somemore/volunteer/service/VolunteerQueryServiceTest.java index 25fc6cbdb..ec0f6dbea 100644 --- a/src/test/java/com/somemore/volunteer/service/VolunteerQueryServiceTest.java +++ b/src/test/java/com/somemore/volunteer/service/VolunteerQueryServiceTest.java @@ -6,7 +6,7 @@ import com.somemore.volunteer.domain.Volunteer; import com.somemore.volunteer.domain.VolunteerDetail; import com.somemore.volunteer.dto.request.VolunteerRegisterRequestDto; -import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.dto.response.VolunteerProfileResponseDto; import com.somemore.volunteer.repository.VolunteerDetailRepository; import com.somemore.volunteer.repository.VolunteerRepository; import org.junit.jupiter.api.DisplayName; @@ -103,7 +103,7 @@ void getMyProfile() { volunteerDetailRepository.save(volunteerDetail); // when - VolunteerResponseDto response = volunteerQueryService.getMyProfile(volunteerId); + VolunteerProfileResponseDto response = volunteerQueryService.getMyProfile(volunteerId); // then assertThat(response).isNotNull(); @@ -123,13 +123,13 @@ void getVolunteerProfile() { volunteerDetailRepository.save(volunteerDetail); // when - VolunteerResponseDto response = volunteerQueryService.getVolunteerProfile(volunteerId); + VolunteerProfileResponseDto response = volunteerQueryService.getVolunteerProfile(volunteerId); // then assertThat(response).isNotNull(); assertThat(response.volunteerId()).isEqualTo(volunteerId.toString()); assertThat(response.nickname()).isEqualTo(volunteer.getNickname()); - assertThat(response.volunteerDetailResponseDto()).isNull(); + assertThat(response.volunteerDetailProfileResponseDto()).isNull(); } @DisplayName("권한이 없는 기관의 봉사자 상세 프로필 조회 실패") From 8d674f82c0b4e47cf2fc9edffbb92606768c72a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:14:01 +0900 Subject: [PATCH 2/8] =?UTF-8?q?build(gradle):=20validation=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 494c337f5..4ba8eef76 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ dependencies { // Data Layer: JPA, Redis, Database Drivers implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-validation' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' From 84b18beff55d60404c79f3a181322951bb410eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:14:52 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat(GlobalExceptionHandler):=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EC=98=88=EC=99=B8=EB=A5=BC=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=20=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC?= =?UTF-8?q?=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Problem Detail 사용. --- .../global/handler/GlobalExceptionHandler.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java b/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java index 48fb90dfc..d734b3d3d 100644 --- a/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java @@ -5,12 +5,12 @@ import com.somemore.global.exception.ImageUploadException; import org.springframework.http.HttpStatus; import org.springframework.http.ProblemDetail; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @RestControllerAdvice -public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { +public class GlobalExceptionHandler { //예시 코드 @ExceptionHandler(BadRequestException.class) @@ -45,4 +45,15 @@ ProblemDetail handleDuplicateException(final DuplicateException e) { return problemDetail; } + @ExceptionHandler(MethodArgumentNotValidException.class) + ProblemDetail handleMethodArgumentNotValid(final MethodArgumentNotValidException e) { + + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage()); + + problemDetail.setTitle("유효성 예외"); + problemDetail.setDetail("입력 데이터 유효성 검사가 실패했습니다. 각 필드를 확인해주세요."); + + return problemDetail; + } + } From 6a5a28690ae508a2be8419cae8ff36773145c362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:15:50 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat(Volunteer):=20update=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 - 닉네임과 소개 필드로 구성된 RequestDto와 ImgUrl로 프로필 업데이트 - Dto의 각 필드 유효한 값인지 검사 --- .../somemore/volunteer/domain/Volunteer.java | 7 ++++++ .../VolunteerProfileUpdateRequestDto.java | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/java/com/somemore/volunteer/dto/request/VolunteerProfileUpdateRequestDto.java diff --git a/src/main/java/com/somemore/volunteer/domain/Volunteer.java b/src/main/java/com/somemore/volunteer/domain/Volunteer.java index a8125c9c7..e98872fa1 100644 --- a/src/main/java/com/somemore/volunteer/domain/Volunteer.java +++ b/src/main/java/com/somemore/volunteer/domain/Volunteer.java @@ -2,6 +2,7 @@ import com.somemore.auth.oauth.OAuthProvider; import com.somemore.global.common.BaseEntity; +import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto; import jakarta.persistence.*; import lombok.*; @@ -58,6 +59,12 @@ public static Volunteer createDefault(OAuthProvider oauthProvider, String oauthI .build(); } + public void updateWith(VolunteerProfileUpdateRequestDto dto, String imgUrl) { + this.nickname = dto.nickname(); + this.introduce = dto.introduce(); + this.imgUrl = imgUrl; + } + @Builder private Volunteer( OAuthProvider oauthProvider, diff --git a/src/main/java/com/somemore/volunteer/dto/request/VolunteerProfileUpdateRequestDto.java b/src/main/java/com/somemore/volunteer/dto/request/VolunteerProfileUpdateRequestDto.java new file mode 100644 index 000000000..88e480ac4 --- /dev/null +++ b/src/main/java/com/somemore/volunteer/dto/request/VolunteerProfileUpdateRequestDto.java @@ -0,0 +1,24 @@ +package com.somemore.volunteer.dto.request; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Builder; + +@Builder +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record VolunteerProfileUpdateRequestDto( + + @Schema(description = "봉사자 닉네임", example = "making") + @NotBlank(message = "닉네임은 필수 값입니다.") + @Size(max = 10, message = "닉네임은 최대 10자까지 입력 가능합니다.") + String nickname, + + @Schema(description = "봉사자 소개글", example = "저는 다양한 봉사활동에 관심이 많은 봉사자입니다.") + @NotBlank(message = "소개글은 필수 값입니다.") + @Size(max = 100, message = "소개글은 최대 100자까지 입력 가능합니다.") + String introduce +) { +} \ No newline at end of file From 80a5e62bb6578cc281f59e1d9a1cfd734bfebc2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:18:25 +0900 Subject: [PATCH 5/8] feat(UpdateVolunteerProfile): update service/usecase --- .../UpdateVolunteerProfileService.java | 33 +++++++++++++++++++ .../UpdateVolunteerProfileUseCase.java | 10 ++++++ 2 files changed, 43 insertions(+) create mode 100644 src/main/java/com/somemore/volunteer/service/UpdateVolunteerProfileService.java create mode 100644 src/main/java/com/somemore/volunteer/usecase/UpdateVolunteerProfileUseCase.java diff --git a/src/main/java/com/somemore/volunteer/service/UpdateVolunteerProfileService.java b/src/main/java/com/somemore/volunteer/service/UpdateVolunteerProfileService.java new file mode 100644 index 000000000..fd2dd5074 --- /dev/null +++ b/src/main/java/com/somemore/volunteer/service/UpdateVolunteerProfileService.java @@ -0,0 +1,33 @@ +package com.somemore.volunteer.service; + +import com.somemore.global.exception.BadRequestException; +import com.somemore.volunteer.domain.Volunteer; +import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto; +import com.somemore.volunteer.repository.VolunteerRepository; +import com.somemore.volunteer.usecase.UpdateVolunteerProfileUseCase; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional +public class UpdateVolunteerProfileService implements UpdateVolunteerProfileUseCase { + + private final VolunteerRepository volunteerRepository; + + @Override + public void update(UUID volunteerId, VolunteerProfileUpdateRequestDto requestDto, String imgUrl) { + Volunteer volunteer = volunteerRepository.findById(volunteerId) + .orElseThrow(() -> new BadRequestException(NOT_EXISTS_VOLUNTEER)); + + volunteer.updateWith(requestDto, imgUrl); + } + +} diff --git a/src/main/java/com/somemore/volunteer/usecase/UpdateVolunteerProfileUseCase.java b/src/main/java/com/somemore/volunteer/usecase/UpdateVolunteerProfileUseCase.java new file mode 100644 index 000000000..330cd502e --- /dev/null +++ b/src/main/java/com/somemore/volunteer/usecase/UpdateVolunteerProfileUseCase.java @@ -0,0 +1,10 @@ +package com.somemore.volunteer.usecase; + +import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto; + +import java.util.UUID; + +public interface UpdateVolunteerProfileUseCase { + + void update(UUID volunteerId, VolunteerProfileUpdateRequestDto requestDto, String imgUrl); +} From 74b535f44ea0abced61faf5b18b37b1877bef375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:19:39 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat(VolunteerProfileCommandController):=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=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 --- .../VolunteerProfileCommandController.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/com/somemore/volunteer/controller/VolunteerProfileCommandController.java diff --git a/src/main/java/com/somemore/volunteer/controller/VolunteerProfileCommandController.java b/src/main/java/com/somemore/volunteer/controller/VolunteerProfileCommandController.java new file mode 100644 index 000000000..dfa9fae6b --- /dev/null +++ b/src/main/java/com/somemore/volunteer/controller/VolunteerProfileCommandController.java @@ -0,0 +1,53 @@ +package com.somemore.volunteer.controller; + +import com.somemore.global.common.response.ApiResponse; +import com.somemore.imageupload.dto.ImageUploadRequestDto; +import com.somemore.imageupload.usecase.ImageUploadUseCase; +import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto; +import com.somemore.volunteer.usecase.UpdateVolunteerProfileUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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; + +import java.util.UUID; + +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + +@RestController +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/api/profile") +@Tag(name = "PUT Volunteer", description = "봉사자 프로필 수정") +public class VolunteerProfileCommandController { + + private final UpdateVolunteerProfileUseCase updateVolunteerProfileUseCase; + private final ImageUploadUseCase imageUploadUseCase; + + @Secured("ROLE_VOLUNTEER") + @Operation(summary = "프로필 수정", description = "현재 로그인된 사용자의 프로필을 수정합니다.") + @PutMapping(consumes = MULTIPART_FORM_DATA_VALUE) + public ApiResponse updateProfile( + @AuthenticationPrincipal String volunteerId, + @Valid @RequestPart("data") VolunteerProfileUpdateRequestDto requestDto, + @RequestPart(value = "img_file", required = false) MultipartFile image) { + + String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image)); + + updateVolunteerProfileUseCase.update( + UUID.fromString(volunteerId), + requestDto, + imgUrl + ); + + return ApiResponse.ok("프로필 수정 성공"); + } +} From 9c15bec0ad44c693aaf74f9b0bbf51892cc530d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:20:23 +0900 Subject: [PATCH 7/8] =?UTF-8?q?test(UpdateVolunteerProfileService):=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UpdateVolunteerProfileServiceTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/test/java/com/somemore/volunteer/service/UpdateVolunteerProfileServiceTest.java diff --git a/src/test/java/com/somemore/volunteer/service/UpdateVolunteerProfileServiceTest.java b/src/test/java/com/somemore/volunteer/service/UpdateVolunteerProfileServiceTest.java new file mode 100644 index 000000000..e7b84d27d --- /dev/null +++ b/src/test/java/com/somemore/volunteer/service/UpdateVolunteerProfileServiceTest.java @@ -0,0 +1,64 @@ +package com.somemore.volunteer.service; + +import com.somemore.IntegrationTestSupport; +import com.somemore.auth.oauth.OAuthProvider; +import com.somemore.volunteer.domain.Volunteer; +import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto; +import com.somemore.volunteer.repository.VolunteerRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@Transactional +class UpdateVolunteerProfileServiceTest extends IntegrationTestSupport { + + @Autowired + private UpdateVolunteerProfileService updateVolunteerProfileService; + + @Autowired + private VolunteerRepository volunteerRepository; + + final String oAuthId = "example-oauth-id"; + final OAuthProvider oAuthProvider = OAuthProvider.NAVER; + final String imgUrl = "http://example.com/updated-image.jpg"; + final VolunteerProfileUpdateRequestDto requestDto = new VolunteerProfileUpdateRequestDto( + "Updated Nickname", + "Updated Introduction" + ); + + + @Test + @DisplayName("봉사자 프로필을 성공적으로 업데이트한다") + void updateVolunteerProfileSuccess() { + // given + Volunteer volunteer = Volunteer.createDefault(oAuthProvider, oAuthId); + volunteerRepository.save(volunteer); + + // when + updateVolunteerProfileService.update(volunteer.getId(), requestDto, imgUrl); + + // then + Volunteer updatedVolunteer = volunteerRepository.findById(volunteer.getId()).orElseThrow(); + assertThat(updatedVolunteer.getNickname()).isEqualTo(requestDto.nickname()); + assertThat(updatedVolunteer.getIntroduce()).isEqualTo(requestDto.introduce()); + assertThat(updatedVolunteer.getImgUrl()).isEqualTo(imgUrl); + } + + @Test + @DisplayName("존재하지 않는 봉사자 ID로 업데이트 시 예외를 던진다") + void updateVolunteerProfileThrowsWhenNotFound() { + // given + // when + // then + assertThatThrownBy(() -> updateVolunteerProfileService.update(UUID.randomUUID(), requestDto, imgUrl)) + .isInstanceOf(com.somemore.global.exception.BadRequestException.class) + .hasMessage(NOT_EXISTS_VOLUNTEER.getMessage()); + } +} \ No newline at end of file From 8ce6885150870e84789fb4ad78c6a74ff4d8fc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=9E=AC=EC=A4=91?= <126754298+m-a-king@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:26:53 +0900 Subject: [PATCH 8/8] =?UTF-8?q?test(VolunteerProfileCommandController):=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EA=B0=92=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=EB=A5=BC=20=ED=8F=AC=ED=95=A8=ED=95=9C=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=ED=86=A8=EB=9F=AC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...VolunteerProfileCommandControllerTest.java | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/test/java/com/somemore/volunteer/controller/VolunteerProfileCommandControllerTest.java diff --git a/src/test/java/com/somemore/volunteer/controller/VolunteerProfileCommandControllerTest.java b/src/test/java/com/somemore/volunteer/controller/VolunteerProfileCommandControllerTest.java new file mode 100644 index 000000000..dca6992bc --- /dev/null +++ b/src/test/java/com/somemore/volunteer/controller/VolunteerProfileCommandControllerTest.java @@ -0,0 +1,182 @@ +package com.somemore.volunteer.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.somemore.ControllerTestSupport; +import com.somemore.WithMockCustomUser; +import com.somemore.imageupload.usecase.ImageUploadUseCase; +import com.somemore.volunteer.dto.request.VolunteerProfileUpdateRequestDto; +import com.somemore.volunteer.usecase.UpdateVolunteerProfileUseCase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class VolunteerProfileCommandControllerTest extends ControllerTestSupport { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private UpdateVolunteerProfileUseCase updateVolunteerProfileUseCase; + + @MockBean + private ImageUploadUseCase imageUploadUseCase; + + @DisplayName("봉사자 프로필 수정 성공 테스트") + @Test + @WithMockCustomUser(role = "VOLUNTEER") + void updateVolunteerProfile() throws Exception { + // given + VolunteerProfileUpdateRequestDto requestDto = VolunteerProfileUpdateRequestDto.builder() + .nickname("making") + .introduce("making is making") + .build(); + + MockMultipartFile imageFile = createMockImageFile(); + MockMultipartFile requestData = createMockRequestData(requestDto); + + String mockImageUrl = "http://example.com/image/profile-image.jpg"; + + given(imageUploadUseCase.uploadImage(any())).willReturn(mockImageUrl); + willDoNothing().given(updateVolunteerProfileUseCase) + .update(any(UUID.class), any(VolunteerProfileUpdateRequestDto.class), anyString()); + + MockMultipartHttpServletRequestBuilder builder = createMockRequestBuilder(); + + // when + mockMvc.perform(builder + .file(requestData) + .file(imageFile) + .contentType(MULTIPART_FORM_DATA) + .header("Authorization", "Bearer access-token")) + // then + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").isEmpty()) + .andExpect(jsonPath("$.message").value("프로필 수정 성공")); + } + + @DisplayName("봉사자 프로필 수정의 닉네임 유효성 검사 실패 테스트") + @Test + @WithMockCustomUser(role = "VOLUNTEER") + void testNicknameValidation() throws Exception { + // given + VolunteerProfileUpdateRequestDto invalidRequestDto = VolunteerProfileUpdateRequestDto.builder() + .nickname("TooLongNickname") // 10자를 초과 + .introduce("Valid introduction") + .build(); + + MockMultipartFile imageFile = createMockImageFile(); + MockMultipartFile requestData = createMockRequestData(invalidRequestDto); + + MockMultipartHttpServletRequestBuilder builder = createMockRequestBuilder(); + + // when + mockMvc.perform(builder + .file(requestData) + .file(imageFile) + .contentType(MULTIPART_FORM_DATA) + .header("Authorization", "Bearer access-token")) + .andExpect(status().isBadRequest()) + .andDo(result -> { + String responseBody = result.getResponse().getContentAsString(StandardCharsets.UTF_8); + + Map jsonResponse = objectMapper.readValue(responseBody, new TypeReference<>() { + }); + String detail = (String) jsonResponse.get("detail"); + + assertThat(detail).isEqualTo("입력 데이터 유효성 검사가 실패했습니다. 각 필드를 확인해주세요."); + }); + } + + @DisplayName("봉사자 프로필 수정의 소개 유효성 검사 실패 테스트") + @Test + @WithMockCustomUser(role = "VOLUNTEER") + void testIntroduceValidation() throws Exception { + // given + VolunteerProfileUpdateRequestDto invalidRequestDto = VolunteerProfileUpdateRequestDto.builder() + .nickname("making") + .introduce(""" + TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_ + TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_ + TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_ + TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_ + TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_ + TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_TOOLONG_ + """) + .build(); + + MockMultipartFile imageFile = createMockImageFile(); + MockMultipartFile requestData = createMockRequestData(invalidRequestDto); + + MockMultipartHttpServletRequestBuilder builder = createMockRequestBuilder(); + + // when + mockMvc.perform(builder + .file(requestData) + .file(imageFile) + .contentType(MULTIPART_FORM_DATA) + .header("Authorization", "Bearer access-token")) + .andExpect(status().isBadRequest()) + .andDo(result -> { + String responseBody = result.getResponse().getContentAsString(StandardCharsets.UTF_8); + + Map jsonResponse = objectMapper.readValue(responseBody, new TypeReference<>() { + }); + String detail = (String) jsonResponse.get("detail"); + + assertThat(detail).isEqualTo("입력 데이터 유효성 검사가 실패했습니다. 각 필드를 확인해주세요."); + }); + } + + private MockMultipartFile createMockImageFile() { + return new MockMultipartFile( + "img_file", + "profile-image.jpg", + MediaType.IMAGE_JPEG_VALUE, + "profile image content".getBytes() + ); + } + + private MockMultipartFile createMockRequestData(Object dto) throws Exception { + return new MockMultipartFile( + "data", + "", + MediaType.APPLICATION_JSON_VALUE, + objectMapper.writeValueAsBytes(dto) + ); + } + + private MockMultipartHttpServletRequestBuilder createMockRequestBuilder() { + MockMultipartHttpServletRequestBuilder builder = multipart("/api/profile"); + builder.with(request -> { + request.setMethod("PUT"); + return request; + }); + return builder; + } +} \ No newline at end of file