diff --git a/src/main/java/com/somemore/center/controller/CenterProfileCommandApiController.java b/src/main/java/com/somemore/center/controller/CenterProfileCommandApiController.java new file mode 100644 index 000000000..2acd0c6d9 --- /dev/null +++ b/src/main/java/com/somemore/center/controller/CenterProfileCommandApiController.java @@ -0,0 +1,43 @@ +package com.somemore.center.controller; + +import com.somemore.auth.annotation.CurrentUser; +import com.somemore.center.dto.request.CenterProfileUpdateRequestDto; +import com.somemore.center.usecase.command.UpdateCenterProfileUseCase; +import com.somemore.global.common.response.ApiResponse; +import com.somemore.imageupload.dto.ImageUploadRequestDto; +import com.somemore.imageupload.usecase.ImageUploadUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.UUID; + +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + +@Tag(name = "Center Command API", description = "센터 프로필 수정 API") +@RequiredArgsConstructor +@RequestMapping("/api/center/profile") +@RestController +public class CenterProfileCommandApiController { + + private final UpdateCenterProfileUseCase updateCenterProfileUseCase; + private final ImageUploadUseCase imageUploadUseCase; + + @Secured("ROLE_CENTER") + @Operation(summary = "센터 프로필 수정", description = "센터 프로필을 수정합니다.") + @PutMapping(consumes = MULTIPART_FORM_DATA_VALUE) + public ApiResponse updateCenterProfile( + @CurrentUser UUID centerId, + @Valid @RequestPart("data") CenterProfileUpdateRequestDto requestDto, + @RequestPart(value = "img_file", required = false) MultipartFile image + ) { + String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image)); + updateCenterProfileUseCase.updateCenterProfile(centerId, requestDto, imgUrl); + + return ApiResponse.ok("센터 프로필 수정 성공"); + } +} diff --git a/src/main/java/com/somemore/center/domain/Center.java b/src/main/java/com/somemore/center/domain/Center.java index a957772fd..43dc4306b 100644 --- a/src/main/java/com/somemore/center/domain/Center.java +++ b/src/main/java/com/somemore/center/domain/Center.java @@ -1,5 +1,6 @@ package com.somemore.center.domain; +import com.somemore.center.dto.request.CenterProfileUpdateRequestDto; import com.somemore.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -63,4 +64,11 @@ public static Center create(String name, String contactNumber, String imgUrl, St .build(); } + public void updateWith(CenterProfileUpdateRequestDto dto, String imgUrl) { + this.name = dto.name(); + this.contactNumber = dto.contactNumber(); + this.homepageLink = dto.homepageLink(); + this.introduce = dto.introduce(); + this.imgUrl = imgUrl; + } } diff --git a/src/main/java/com/somemore/center/dto/request/CenterProfileUpdateRequestDto.java b/src/main/java/com/somemore/center/dto/request/CenterProfileUpdateRequestDto.java new file mode 100644 index 000000000..e9ee61fc6 --- /dev/null +++ b/src/main/java/com/somemore/center/dto/request/CenterProfileUpdateRequestDto.java @@ -0,0 +1,24 @@ +package com.somemore.center.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 lombok.Builder; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@Builder +public record CenterProfileUpdateRequestDto( + @Schema(description = "센터 이름", example = "서울 도서관") + @NotBlank(message = "센터 이름은 필수 값입니다.") + String name, + @Schema(description = "연락처", example = "010-1234-5678") + @NotBlank(message = "연락처는 필수 값입니다.") + String contactNumber, + @Schema(description = "센터 홈페이지 링크", example = "https://fitnesscenter.com") + @NotBlank(message = "센터 홈페이지 링크는 필수 값입니다.") + String homepageLink, + @Schema(description = "센터 소개", example = "저희 도서관은 유명해요") + @NotBlank(message = "센터 소개는 필수 값입니다.") + String introduce +) {} diff --git a/src/main/java/com/somemore/center/service/command/UpdateCenterProfileService.java b/src/main/java/com/somemore/center/service/command/UpdateCenterProfileService.java new file mode 100644 index 000000000..cdd35e4d6 --- /dev/null +++ b/src/main/java/com/somemore/center/service/command/UpdateCenterProfileService.java @@ -0,0 +1,30 @@ +package com.somemore.center.service.command; + +import com.somemore.center.domain.Center; +import com.somemore.center.dto.request.CenterProfileUpdateRequestDto; +import com.somemore.center.repository.CenterRepository; +import com.somemore.center.usecase.command.UpdateCenterProfileUseCase; +import com.somemore.global.exception.BadRequestException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER; + +@RequiredArgsConstructor +@Transactional +@Service +public class UpdateCenterProfileService implements UpdateCenterProfileUseCase { + + private final CenterRepository centerRepository; + + @Override + public void updateCenterProfile(UUID centerId, CenterProfileUpdateRequestDto requestDto, String imgUrl) { + Center center = centerRepository.findCenterById(centerId) + .orElseThrow(() -> new BadRequestException(NOT_EXISTS_CENTER)); + + center.updateWith(requestDto, imgUrl); + } +} diff --git a/src/main/java/com/somemore/center/usecase/command/UpdateCenterProfileUseCase.java b/src/main/java/com/somemore/center/usecase/command/UpdateCenterProfileUseCase.java new file mode 100644 index 000000000..df5905fc3 --- /dev/null +++ b/src/main/java/com/somemore/center/usecase/command/UpdateCenterProfileUseCase.java @@ -0,0 +1,9 @@ +package com.somemore.center.usecase.command; + +import com.somemore.center.dto.request.CenterProfileUpdateRequestDto; + +import java.util.UUID; + +public interface UpdateCenterProfileUseCase { + void updateCenterProfile(UUID centerId, CenterProfileUpdateRequestDto requestDto, String imgUrl); +} diff --git a/src/test/java/com/somemore/center/controller/CenterProfileCommandApiControllerTest.java b/src/test/java/com/somemore/center/controller/CenterProfileCommandApiControllerTest.java new file mode 100644 index 000000000..7adf89a45 --- /dev/null +++ b/src/test/java/com/somemore/center/controller/CenterProfileCommandApiControllerTest.java @@ -0,0 +1,101 @@ +package com.somemore.center.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.somemore.ControllerTestSupport; +import com.somemore.WithMockCustomUser; +import com.somemore.center.dto.request.CenterProfileUpdateRequestDto; +import com.somemore.center.usecase.command.UpdateCenterProfileUseCase; +import com.somemore.imageupload.usecase.ImageUploadUseCase; +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.MockHttpServletRequest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +import java.util.UUID; + +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 CenterProfileCommandApiControllerTest extends ControllerTestSupport { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private UpdateCenterProfileUseCase updateCenterProfileUseCase; + + @MockBean + private ImageUploadUseCase imageUploadUseCase; + + @Test + @DisplayName("센터 프로필 수정 성공 테스트") + @WithMockCustomUser(role = "CENTER") + void updateCenterProfile_success() throws Exception { + //given + CenterProfileUpdateRequestDto requestDto = CenterProfileUpdateRequestDto.builder() + .name("테스트 센터명") + .contactNumber("010-0000-0000") + .homepageLink("https://www.test.com") + .introduce("테스트 설명") + .build(); + + MockMultipartFile imageFile = new MockMultipartFile( + "img_file", + "test-image.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image content".getBytes() + ); + + MockMultipartFile requestData = new MockMultipartFile( + "data", + "", + MediaType.APPLICATION_JSON_VALUE, + objectMapper.writeValueAsBytes(requestDto) + ); + + String imageUrl = "http://example.com/image/test-image.jpg"; + + given(imageUploadUseCase.uploadImage(any())).willReturn(imageUrl); + willDoNothing().given(updateCenterProfileUseCase) + .updateCenterProfile(any(UUID.class), any(), anyString()); + + MockMultipartHttpServletRequestBuilder builder = multipart("/api/center/profile"); + builder.with(new RequestPostProcessor() { + @Override + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + request.setMethod("PUT"); + return request; + } + }); + + //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("센터 프로필 수정 성공")); + } +} diff --git a/src/test/java/com/somemore/center/service/command/UpdateCenterProfileServiceTest.java b/src/test/java/com/somemore/center/service/command/UpdateCenterProfileServiceTest.java new file mode 100644 index 000000000..a477f83d0 --- /dev/null +++ b/src/test/java/com/somemore/center/service/command/UpdateCenterProfileServiceTest.java @@ -0,0 +1,69 @@ +package com.somemore.center.service.command; + +import com.somemore.IntegrationTestSupport; +import com.somemore.center.domain.Center; +import com.somemore.center.dto.request.CenterProfileUpdateRequestDto; +import com.somemore.center.repository.CenterRepository; +import com.somemore.global.exception.BadRequestException; +import org.assertj.core.api.ThrowableAssert; +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.common.fixture.CenterFixture.createCenter; +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER; +import static org.assertj.core.api.Assertions.*; + +@Transactional +class UpdateCenterProfileServiceTest extends IntegrationTestSupport { + + @Autowired + private UpdateCenterProfileService updateCenterProfileService; + + @Autowired + private CenterRepository centerRepository; + + CenterProfileUpdateRequestDto requestDto = CenterProfileUpdateRequestDto.builder() + .name("테스트 센터명") + .contactNumber("010-0000-0000") + .homepageLink("https://www.test.com") + .introduce("테스트 설명") + .build(); + + String imgUrl = "http://example.com/updated-image.jpg"; + + @Test + @DisplayName("센터 프로필을 업데이트 한다.") + void updateCenterProfile() { + //given + Center center = createCenter(); + centerRepository.save(center); + + //when + updateCenterProfileService.updateCenterProfile(center.getId(), requestDto, imgUrl); + + //then + Center updatedCenter = centerRepository.findCenterById(center.getId()).orElseThrow(); + assertThat(updatedCenter.getName()).isEqualTo(requestDto.name()); + assertThat(updatedCenter.getContactNumber()).isEqualTo(requestDto.contactNumber()); + assertThat(updatedCenter.getHomepageLink()).isEqualTo(requestDto.homepageLink()); + assertThat(updatedCenter.getIntroduce()).isEqualTo(requestDto.introduce()); + assertThat(updatedCenter.getImgUrl()).isEqualTo(imgUrl); + } + + @Test + @DisplayName("존재하지 않는 센터 ID로 업데이트 시 예외를 던진다") + void updateCenterProfileWithInvalidId() { + //given + //when + ThrowableAssert.ThrowingCallable callable = () -> updateCenterProfileService.updateCenterProfile(UUID.randomUUID(), requestDto, imgUrl); + + //then + assertThatExceptionOfType(BadRequestException.class) + .isThrownBy(callable) + .withMessage(NOT_EXISTS_CENTER.getMessage()); + } +}