From fb3b225f62034265f493f5eab1201f76e11ba2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=8F=84=EB=AA=A8?= Date: Fri, 7 Jul 2023 15:22:05 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[DPMBE-100]=20=EC=95=BD=EC=86=8D=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EC=97=90=20Coordainate(=EC=9C=84?= =?UTF-8?q?=EA=B2=BD=EB=8F=84)=EB=A5=BC=20=EC=A0=9C=EA=B1=B0=ED=95=9C?= =?UTF-8?q?=EB=8B=A4=20(#151)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 약속 이미지에 Coordainate(위경도)를 제거한다 * style: spotless * test: Test Code에서 Coordainate 제거 --- .../api/image/controller/ImageController.kt | 7 +------ .../image/usecase/ImageUploadSuccessUseCase.kt | 4 +--- .../PromiseImageUploadSuccessUseCaseTest.kt | 5 +---- .../whatnow/domains/image/domain/PromiseImage.kt | 8 +------- .../domains/image/service/ImageDomainService.kt | 4 +--- .../adapter/PromisePromiseImageAdapterTest.kt | 5 +---- .../service/PromiseImageDomainServiceTest.kt | 15 +++++---------- ...PromisePromiseImageRegisterEventHandlerTest.kt | 3 +-- 8 files changed, 12 insertions(+), 39 deletions(-) diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt index c213ce17..ff4a3b9a 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt @@ -5,7 +5,6 @@ import com.depromeet.whatnow.api.image.dto.ImageUrlResponse import com.depromeet.whatnow.api.image.usecase.GetPresignedUrlUseCase import com.depromeet.whatnow.api.image.usecase.ImageCommentReadUseCase import com.depromeet.whatnow.api.image.usecase.ImageUploadSuccessUseCase -import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import io.swagger.v3.oas.annotations.Operation @@ -14,11 +13,9 @@ import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @Tag(name = "6. [이미지]") @@ -52,10 +49,8 @@ class ImageController( @PathVariable promiseId: Long, @PathVariable imageKey: String, @RequestParam promiseImageCommentType: PromiseImageCommentType, - @RequestBody @Valid - userLocation: CoordinateVo, ) { - successUseCase.promiseUploadImageSuccess(promiseId, imageKey, promiseImageCommentType, userLocation) + successUseCase.promiseUploadImageSuccess(promiseId, imageKey, promiseImageCommentType) } @Operation(summary = "유저 프로필 이미지 업로드 성공 요청") diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt index cd5b78d7..8ae6a897 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt @@ -1,7 +1,6 @@ package com.depromeet.whatnow.api.image.usecase import com.depromeet.whatnow.annotation.UseCase -import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.config.security.SecurityUtils import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.service.ImageDomainService @@ -14,10 +13,9 @@ class ImageUploadSuccessUseCase( promiseId: Long, imageKey: String, promiseImageCommentType: PromiseImageCommentType, - userLocation: CoordinateVo, ) { val currentUserId: Long = SecurityUtils.currentUserId - imageDomainService.promiseImageUploadSuccess(currentUserId, promiseId, imageKey, promiseImageCommentType, userLocation) + imageDomainService.promiseImageUploadSuccess(currentUserId, promiseId, imageKey, promiseImageCommentType) } fun userUploadImageSuccess(imageKey: String) { diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt index 664f47fb..7892e2a9 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt @@ -1,6 +1,5 @@ package com.depromeet.whatnow.api.image.usecase -import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.service.ImageDomainService import org.assertj.core.api.Assertions.assertThatCode @@ -33,13 +32,11 @@ class PromiseImageUploadSuccessUseCaseTest { @Test fun `약속 이미지 업로드 성공 요청시 정상적이라면 에러가 발생하지 않는다`() { // given - val coordinateVo = CoordinateVo(1.0, 1.0) - // when // then assertThatCode { - imageUploadSuccessUseCase.promiseUploadImageSuccess(1, "imageKey", PromiseImageCommentType.SORRY_LATE, coordinateVo) + imageUploadSuccessUseCase.promiseUploadImageSuccess(1, "imageKey", PromiseImageCommentType.SORRY_LATE) }.doesNotThrowAnyException() } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt index 21863485..3c8410a3 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt @@ -2,10 +2,8 @@ package com.depromeet.whatnow.domains.image.domain import com.depromeet.whatnow.common.BaseTimeEntity import com.depromeet.whatnow.common.aop.event.Events -import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.events.domainEvent.PromiseImageRegisterEvent import javax.persistence.Column -import javax.persistence.Embedded import javax.persistence.Entity import javax.persistence.EnumType import javax.persistence.Enumerated @@ -29,9 +27,6 @@ class PromiseImage( @Enumerated(EnumType.STRING) var promiseImageCommentType: PromiseImageCommentType, - @Embedded - var userLocation: CoordinateVo? = null, - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "promise_image_id") @@ -44,9 +39,8 @@ class PromiseImage( uri: String, imageKey: String, promiseImageCommentType: PromiseImageCommentType, - userLocation: CoordinateVo, ): PromiseImage { - return PromiseImage(userId, promiseId, uri, imageKey, promiseImageCommentType, userLocation) + return PromiseImage(userId, promiseId, uri, imageKey, promiseImageCommentType) } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt index 8247afb9..84b60438 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt @@ -1,6 +1,5 @@ package com.depromeet.whatnow.domains.image.service -import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.consts.IMAGE_DOMAIN import com.depromeet.whatnow.domains.image.adapter.PromiseImageAdapter import com.depromeet.whatnow.domains.image.adapter.UserImageAdapter @@ -31,14 +30,13 @@ class ImageDomainService( promiseId: Long, imageKey: String, promiseImageCommentType: PromiseImageCommentType, - userLocation: CoordinateVo, ) { val promiseUser = promiseUserAdapter.findByPromiseIdAndUserId(promiseId, userId) validatePromiseUserType(promiseUser.promiseUserType!!, promiseImageCommentType) val imageUrl = IMAGE_DOMAIN + "/" + springEnvironmentHelper.activeProfile + "/" + "promise/$promiseId/$imageKey" promiseImageAdapter.save( - PromiseImage.of(promiseId, userId, imageUrl, imageKey, promiseImageCommentType, userLocation), + PromiseImage.of(promiseId, userId, imageUrl, imageKey, promiseImageCommentType), ) } diff --git a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt index 19f08cd7..a75c84cf 100644 --- a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt +++ b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt @@ -1,6 +1,5 @@ package com.depromeet.whatnow.domains.image.adapter -import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.domains.image.domain.PromiseImage import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.repository.PromiseImageRepository @@ -23,8 +22,7 @@ class PromisePromiseImageAdapterTest { @Test fun `약속 이미지 저장 시 정상적으로 저장된다`() { - val userLocation = CoordinateVo(37.2, 128.05) - val promiseImage = PromiseImage.of(1, 1, "imageUri", "imageKey", PromiseImageCommentType.RUNNING, userLocation) + val promiseImage = PromiseImage.of(1, 1, "imageUri", "imageKey", PromiseImageCommentType.RUNNING) given(promiseImageRepository.save(Mockito.any(PromiseImage::class.java))) .willReturn(promiseImage) @@ -37,6 +35,5 @@ class PromisePromiseImageAdapterTest { assertEquals(savedPromiseImage.uri, "imageUri") assertEquals(savedPromiseImage.imageKey, "imageKey") assertEquals(savedPromiseImage.promiseImageCommentType, PromiseImageCommentType.RUNNING) - assertEquals(savedPromiseImage.userLocation, userLocation) } } diff --git a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt index faa2de8c..e5c6345a 100644 --- a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt +++ b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt @@ -50,13 +50,12 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.LATE, ) - val userLocation = CoordinateVo(37.2, 128.05) given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatCode { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING, userLocation) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) }.doesNotThrowAnyException() } @@ -69,13 +68,12 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.READY, ) - val userLocation = CoordinateVo(37.2, 128.05) given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING, userLocation) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) }.isInstanceOf(UploadBeforeTrackingException::class.java) } @@ -88,13 +86,12 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.CANCEL, ) - val userLocation = CoordinateVo(37.2, 128.05) given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING, userLocation) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) }.isInstanceOf(CancelledUserUploadException::class.java) } @@ -107,13 +104,12 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.LATE, ) - val userLocation = CoordinateVo(37.2, 128.05) given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.DID_YOU_COME, userLocation) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.DID_YOU_COME) }.isInstanceOf(InvalidCommentTypeException::class.java) } @@ -126,13 +122,12 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.WAIT, ) - val userLocation = CoordinateVo(37.2, 128.05) given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.WAIT_A_BIT, userLocation) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.WAIT_A_BIT) }.isInstanceOf(InvalidCommentTypeException::class.java) } } diff --git a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromisePromiseImageRegisterEventHandlerTest.kt b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromisePromiseImageRegisterEventHandlerTest.kt index fc0d81c2..fc140277 100644 --- a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromisePromiseImageRegisterEventHandlerTest.kt +++ b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromisePromiseImageRegisterEventHandlerTest.kt @@ -34,11 +34,10 @@ class PromisePromiseImageRegisterEventHandlerTest { fun `약속 이미지 등록 성공 시 이미지 등록 이벤트가 발행되어야한다`() { // given val promiseUser = PromiseUser(1, 1, CoordinateVo(1.0, 1.0), PromiseUserType.LATE) - val userLocation = CoordinateVo(37.2, 128.05) given(promiseUserAdaptor.findByPromiseIdAndUserId(1, 1)).willReturn(promiseUser) // when - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING, userLocation) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) // then then(imageRegisterEventHandler).should(Mockito.times(1)).handleRegisterPictureEvent(any()) From 26ad15bd7e3cc37522b73665d4e9dd29127208ed Mon Sep 17 00:00:00 2001 From: BlackBean99 <54030889+BlackBean99@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:35:22 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[DPMBE-94]=20=EC=95=BD=EC=86=8D=EB=82=B4=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=9C=84=EC=B9=98=EB=A5=BC=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=ED=99=94=ED=95=9C=EB=8B=A4=20(#149)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : user 위치 갱신 * feat : 활성화된 약속만 수행 aop * refactor : exception 생성자 통일 * refactor: spotless --- .../annotation/RequiresMainUserAspect.kt | 1 + .../controller/PromiseUserController.kt | 8 +++ .../api/promiseuser/dto/PromiseLocationDto.kt | 21 ++++++++ .../usecase/PromiseUserRecordUseCase.kt | 22 ++++++++ .../common/aop/verify/ActivePromise.kt | 5 ++ .../common/aop/verify/ActivePromiseAop.kt | 53 +++++++++++++++++++ .../whatnow/domains/promise/domain/Promise.kt | 4 ++ .../promise/exception/PromiseErrorCode.kt | 7 ++- .../exception/PromiseNotActivateException.kt | 11 ++++ .../exception/PromiseNotFoundException.kt | 3 +- .../PromiseNotParticipateException.kt | 12 +++++ .../service/PromiseUserDomainService.kt | 3 ++ 12 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/dto/PromiseLocationDto.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromise.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromiseAop.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotActivateException.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotParticipateException.kt diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/annotation/RequiresMainUserAspect.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/annotation/RequiresMainUserAspect.kt index 461eb7cf..8c5cbd26 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/annotation/RequiresMainUserAspect.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/annotation/RequiresMainUserAspect.kt @@ -18,6 +18,7 @@ class RequiresMainUserAspect( val userId = SecurityUtils.currentUserId val promiseId = joinPoint.args[0] as Long val promise = promiseAdaptor.queryPromise(promiseId) + if (userId == promise.mainUserId) { return joinPoint.proceed() } else { diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/controller/PromiseUserController.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/controller/PromiseUserController.kt index bd4f3855..d511649b 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/controller/PromiseUserController.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/controller/PromiseUserController.kt @@ -1,5 +1,6 @@ package com.depromeet.whatnow.api.promiseuser.controller +import com.depromeet.whatnow.api.promiseuser.dto.PromiseLocationDto import com.depromeet.whatnow.api.promiseuser.dto.PromiseUserDto import com.depromeet.whatnow.api.promiseuser.usecase.PromiseUserReadUseCase import com.depromeet.whatnow.api.promiseuser.usecase.PromiseUserRecordUseCase @@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController @@ -46,4 +48,10 @@ class PromiseUserController( fun cancelPromise(@PathVariable("promise-id") promiseId: Long, @PathVariable("user-id") userId: Long, @PathVariable("status") promiseUserType: PromiseUserType): PromiseUserDto { return promiseUserRecordUseCase.updatePromiseUserType(promiseId, userId, promiseUserType) } + + @Operation(summary = "내 위치 갱신하기", description = "내 위치를 갱신하고 최신화된 약속의 참여한 유저들의 위치를 반환합니다., (GRS84GEO(위경도) 단위로 요청하셔야 합니다") + @PutMapping("/promises/{promise-id}/users/location") + fun updateLocation(@PathVariable("promise-id") promiseId: Long, @RequestParam userLocation: CoordinateVo): List { + return promiseUserRecordUseCase.updateLocation(promiseId, userLocation) + } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/dto/PromiseLocationDto.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/dto/PromiseLocationDto.kt new file mode 100644 index 00000000..8a857d03 --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/dto/PromiseLocationDto.kt @@ -0,0 +1,21 @@ +package com.depromeet.whatnow.api.promiseuser.dto + +import com.depromeet.whatnow.common.vo.CoordinateVo +import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUser +import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType + +data class PromiseLocationDto( + val userId: Long, + val userLocation: CoordinateVo, + val promiseUserType: PromiseUserType, +) { + companion object { + fun from(promiseUser: PromiseUser): PromiseLocationDto { + return PromiseLocationDto( + userId = promiseUser.userId, + userLocation = promiseUser.userLocation!!, + promiseUserType = promiseUser.promiseUserType!!, + ) + } + } +} diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/usecase/PromiseUserRecordUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/usecase/PromiseUserRecordUseCase.kt index 6c8710a7..4a9aab36 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/usecase/PromiseUserRecordUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promiseuser/usecase/PromiseUserRecordUseCase.kt @@ -1,17 +1,23 @@ package com.depromeet.whatnow.api.promiseuser.usecase import com.depromeet.whatnow.annotation.UseCase +import com.depromeet.whatnow.api.promiseuser.dto.PromiseLocationDto import com.depromeet.whatnow.api.promiseuser.dto.PromiseUserDto +import com.depromeet.whatnow.common.aop.verify.ActivePromise import com.depromeet.whatnow.common.vo.CoordinateVo +import com.depromeet.whatnow.config.security.SecurityUtils import com.depromeet.whatnow.domains.progresshistory.domain.PromiseProgress.DEFAULT +import com.depromeet.whatnow.domains.promise.exception.PromiseNotParticipateException import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUser import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType import com.depromeet.whatnow.domains.promiseuser.service.PromiseUserDomainService +import org.springframework.transaction.annotation.Transactional @UseCase class PromiseUserRecordUseCase( val promiseUserDomainService: PromiseUserDomainService, ) { + @Transactional fun createPromiseUser(promiseId: Long, userId: Long, userLocation: CoordinateVo): PromiseUserDto { return PromiseUserDto.of( promiseUserDomainService.createPromiseUser( @@ -39,4 +45,20 @@ class PromiseUserRecordUseCase( progress = DEFAULT, ) } + + @Transactional + @ActivePromise + fun updateLocation(promiseId: Long, userLocation: CoordinateVo): List { + val userId = SecurityUtils.currentUserId + val promiseUser = promiseUserDomainService.findByUserId(userId) + .firstOrNull { it.promiseId == promiseId } + ?: throw PromiseNotParticipateException() + + promiseUser.updatePromiseUserLocation(userLocation) + + val promiseUsers = promiseUserDomainService.findByPromiseId(promiseId) + .map { if (it.userId == promiseUser.userId) promiseUser else it } + + return promiseUsers.map(PromiseLocationDto::from) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromise.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromise.kt new file mode 100644 index 00000000..7619fbfa --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromise.kt @@ -0,0 +1,5 @@ +package com.depromeet.whatnow.common.aop.verify + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class ActivePromise diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromiseAop.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromiseAop.kt new file mode 100644 index 00000000..7f467af1 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/common/aop/verify/ActivePromiseAop.kt @@ -0,0 +1,53 @@ +package com.depromeet.whatnow.common.aop.verify + +import com.depromeet.whatnow.domains.promise.adaptor.PromiseAdaptor +import com.depromeet.whatnow.domains.promise.exception.PromiseNotActivateException +import com.depromeet.whatnow.exception.custom.PromiseIdConversionException +import com.depromeet.whatnow.exception.custom.PromiseIdParameterNotFoundException +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation.Around +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.reflect.MethodSignature +import org.springframework.stereotype.Component +import java.lang.NumberFormatException +import java.time.LocalDateTime + +@Aspect +@Component +class ActivePromiseAop( + val promiseAdaptor: PromiseAdaptor, +) { + @Around("@annotation(activePromise)") + fun validateActivePromise(joinPoint: ProceedingJoinPoint, activePromise: ActivePromise): Any? { + val args = joinPoint.args + val signature = joinPoint.signature as MethodSignature + val now = LocalDateTime.now() + val promiseId = findPromiseIdArg(signature.parameterNames, args) + + val promise = promiseAdaptor.queryPromise(promiseId) + if (promise.isActive(now)) { + return joinPoint.proceed() + } else { + throw PromiseNotActivateException() + } + } + + private fun findPromiseIdArg(methodParameterNames: Array, args: Array): Long { + for (i in methodParameterNames.indices) { + if ((methodParameterNames[i] == "promiseId")) { + when (val arg = args[i]) { + is Long -> return arg + is String -> { + try { + return arg.toLong() + } catch (e: NumberFormatException) { + throw PromiseIdConversionException.EXCEPTION + } + } + else -> PromiseIdParameterNotFoundException.EXCEPTION + } + } + } + throw PromiseIdParameterNotFoundException.EXCEPTION + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/domain/Promise.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/domain/Promise.kt index 8c20c8a1..f6b8d7c2 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/domain/Promise.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/domain/Promise.kt @@ -86,4 +86,8 @@ class Promise( this.meetPlace = meetPlace Events.raise(PromiseUpdateMeetPlaceEvent(this.id!!, this.meetPlace!!)) } + + fun isActive(currentTime: LocalDateTime): Boolean { + return this.endTime.minusHours(1) < currentTime && currentTime < this.endTime.plusMinutes(30) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseErrorCode.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseErrorCode.kt index 7a654cb9..0a975c79 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseErrorCode.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseErrorCode.kt @@ -27,10 +27,15 @@ enum class PromiseErrorCode(val status: Int, val code: String, val reason: Strin @ExplainError("이중약속을 잡은 경우") PROMISE_DOUBLE_PROMISE(BAD_REQUEST, "PROMISE_400_2", "이미 약속을 잡았습니다."), + + @ExplainError("활성화(약속시간 1시간 전부터 30분 후까지)된 약속이 아닌 경우") + PROMISE_NOT_ACTIVATE(BAD_REQUEST, "PROMISE_400_3", "약속이 활성화 되지 않았습니다."), ; override val errorReason: ErrorReason - get() { return ErrorReason(status, code, reason) } + get() { + return ErrorReason(status, code, reason) + } override val explainError: String get() { diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotActivateException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotActivateException.kt new file mode 100644 index 00000000..a004b9e9 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotActivateException.kt @@ -0,0 +1,11 @@ +package com.depromeet.whatnow.domains.promise.exception + +import com.depromeet.whatnow.exception.WhatnowCodeException + +class PromiseNotActivateException : WhatnowCodeException( + PromiseErrorCode.PROMISE_NOT_ACTIVATE, +) { + companion object { + val EXCEPTION: WhatnowCodeException = PromiseNotActivateException() + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotFoundException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotFoundException.kt index 1ac05c45..be5b4aae 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotFoundException.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotFoundException.kt @@ -1,10 +1,9 @@ package com.depromeet.whatnow.domains.promise.exception -import com.depromeet.whatnow.exception.GlobalErrorCode import com.depromeet.whatnow.exception.WhatnowCodeException class PromiseNotFoundException : WhatnowCodeException( - GlobalErrorCode.REFRESH_TOKEN_EXPIRED, + PromiseErrorCode.PROMISE_NOT_FOUND, ) { companion object { val EXCEPTION: WhatnowCodeException = PromiseNotFoundException() diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotParticipateException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotParticipateException.kt new file mode 100644 index 00000000..75da87b5 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promise/exception/PromiseNotParticipateException.kt @@ -0,0 +1,12 @@ +package com.depromeet.whatnow.domains.promise.exception + +import com.depromeet.whatnow.exception.GlobalErrorCode +import com.depromeet.whatnow.exception.WhatnowCodeException + +class PromiseNotParticipateException : WhatnowCodeException( + GlobalErrorCode.USER_NOT_PARTICIPATE, +) { + companion object { + val EXCEPTION: WhatnowCodeException = PromiseNotParticipateException() + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt index a7cc56fa..0658a102 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt @@ -45,6 +45,9 @@ class PromiseUserDomainService( fun findByPromiseId(promiseId: Long): List { return promiseUserAdaptor.findByPromiseId(promiseId) } + fun findByUserId(userId: Long): List { + return promiseUserAdaptor.findByUserId(userId) + } fun isArrived(promiseUser: PromiseUser, coordinate: CoordinateVo): Boolean { val start = S2LatLng.fromDegrees(promiseUser.userLocation!!.latitude, promiseUser.userLocation!!.longitude) val destination = S2LatLng.fromDegrees(coordinate.latitude, coordinate.longitude) From ea4c7f4b85447b087e6e12587bdaa0659f7393df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=8F=84=EB=AA=A8?= Date: Fri, 7 Jul 2023 16:21:42 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[DPMBE-99]=20=EC=95=BD=EC=86=8D=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4=20(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 약속 이미지 조회하는 기능을 추가한다 * remove: 이미지 전체 조회 API 삭제 (약속 모음집 상세 API에 추가 할 예정) * test: ImageControllerTest에 PromiseImageReadUseCase MockBean 추가 --- .../api/image/controller/ImageController.kt | 60 ++++++---- .../image/dto/PromiseImageDetailResponse.kt | 15 +++ .../whatnow/api/image/dto/PromiseImageDto.kt | 15 +++ .../api/image/dto/PromiseImageResponse.kt | 11 ++ .../image/usecase/PromiseImageReadUseCase.kt | 58 ++++++++++ ...ntrollerTest.kt => ImageControllerTest.kt} | 10 +- .../usecase/PromiseImageReadUseCaseTest.kt | 104 ++++++++++++++++++ .../image/adapter/PromiseImageAdapter.kt | 8 ++ .../repository/PromiseImageRepository.kt | 5 +- .../image/service/ImageDomainService.kt | 9 ++ .../service/PromiseUserDomainService.kt | 4 + 11 files changed, 276 insertions(+), 23 deletions(-) create mode 100644 Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDetailResponse.kt create mode 100644 Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDto.kt create mode 100644 Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageResponse.kt create mode 100644 Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCase.kt rename Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/{PromiseImageControllerTest.kt => ImageControllerTest.kt} (92%) create mode 100644 Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt index ff4a3b9a..3dea4031 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt @@ -2,9 +2,12 @@ package com.depromeet.whatnow.api.image.controller import com.depromeet.whatnow.api.image.dto.ImageCommentElement import com.depromeet.whatnow.api.image.dto.ImageUrlResponse +import com.depromeet.whatnow.api.image.dto.PromiseImageDetailResponse +import com.depromeet.whatnow.api.image.dto.PromiseImageResponse import com.depromeet.whatnow.api.image.usecase.GetPresignedUrlUseCase import com.depromeet.whatnow.api.image.usecase.ImageCommentReadUseCase import com.depromeet.whatnow.api.image.usecase.ImageUploadSuccessUseCase +import com.depromeet.whatnow.api.image.usecase.PromiseImageReadUseCase import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import io.swagger.v3.oas.annotations.Operation @@ -18,16 +21,17 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController -@Tag(name = "6. [이미지]") -@RequestMapping("/v1") +@RequestMapping("/v1/images") @SecurityRequirement(name = "access-token") class ImageController( val getPresignedUrlUseCase: GetPresignedUrlUseCase, val successUseCase: ImageUploadSuccessUseCase, val imageCommentReadUseCase: ImageCommentReadUseCase, + val promiseImageReadUseCase: PromiseImageReadUseCase, ) { + @Tag(name = "6-1 [약속 이미지]") @Operation(summary = "약속 이미지 업로드 Presigned URL 발급") - @GetMapping("/images/promises/{promiseId}") + @GetMapping("/promises/{promiseId}/presigned-url") fun getPresignedUrlOfPromise( @PathVariable promiseId: Long, @RequestParam fileExtension: ImageFileExtension, @@ -35,16 +39,9 @@ class ImageController( return getPresignedUrlUseCase.forPromise(promiseId, fileExtension) } - @Operation(summary = "유저 프로필 이미지 업로드 Presigned URL 발급") - @GetMapping("/images/users/me") - fun getPresignedUrlOfUser( - @RequestParam fileExtension: ImageFileExtension, - ): ImageUrlResponse { - return getPresignedUrlUseCase.forUser(fileExtension) - } - + @Tag(name = "6-1 [약속 이미지]") @Operation(summary = "약속 이미지 업로드 성공 요청") - @PostMapping("/images/{imageKey}/promises/{promiseId}") + @PostMapping("/{imageKey}/promises/{promiseId}") fun promiseUploadImageSuccess( @PathVariable promiseId: Long, @PathVariable imageKey: String, @@ -53,15 +50,40 @@ class ImageController( successUseCase.promiseUploadImageSuccess(promiseId, imageKey, promiseImageCommentType) } - @Operation(summary = "유저 프로필 이미지 업로드 성공 요청") - @PostMapping("/images/{imageKey}/users/me") - fun userUploadImageSuccess(@PathVariable imageKey: String) { - successUseCase.userUploadImageSuccess(imageKey) - } - + @Tag(name = "6-1 [약속 이미지]") @Operation(summary = "이미지 코멘트를 이넘으로 확인합니다. 주의!! 스웨거 이넘 예시 값과 실제 요청했을 때 값이 달라요 실제 요청값 기준으로 해주세요") - @GetMapping("/images/comments") + @GetMapping("/comments") fun getImageCommentType(): List { return imageCommentReadUseCase.execute() } + + @Tag(name = "6-1 [약속 이미지]") + @Operation(summary = "약속 아이디를 통해 모든 이미지를 LATE, WAIT로 구분하여 가져옵니다.") + @GetMapping("/promises/{promiseId}") + fun getLateAndWaitImagesByPromiseId(@PathVariable promiseId: Long): PromiseImageResponse { + return promiseImageReadUseCase.getLateAndWaitImagesByPromiseId(promiseId) + } + + @Tag(name = "6-1 [약속 이미지]") + @Operation(summary = "이미지 키를 통해 이미지를 가져옵니다.") + @GetMapping("/{imageKey}") + fun getImageByImageKey(@PathVariable imageKey: String): PromiseImageDetailResponse { + return promiseImageReadUseCase.getImageByImageKey(imageKey) + } + + @Tag(name = "6-2 [유저 이미지]") + @Operation(summary = "유저 프로필 이미지 업로드 Presigned URL 발급") + @GetMapping("/users/me/presigned-url") + fun getPresignedUrlOfUser( + @RequestParam fileExtension: ImageFileExtension, + ): ImageUrlResponse { + return getPresignedUrlUseCase.forUser(fileExtension) + } + + @Tag(name = "6-2 [유저 이미지]") + @Operation(summary = "유저 프로필 이미지 업로드 성공 요청") + @PostMapping("/{imageKey}/users/me") + fun userUploadImageSuccess(@PathVariable imageKey: String) { + successUseCase.userUploadImageSuccess(imageKey) + } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDetailResponse.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDetailResponse.kt new file mode 100644 index 00000000..45b4d06f --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDetailResponse.kt @@ -0,0 +1,15 @@ +package com.depromeet.whatnow.api.image.dto + +import com.depromeet.whatnow.domains.image.domain.PromiseImage +import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType + +data class PromiseImageDetailResponse( + val uploadUserId: Long, + val promiseImageCommentType: PromiseImageCommentType, +) { + companion object { + fun from(promiseImage: PromiseImage): PromiseImageDetailResponse { + return PromiseImageDetailResponse(promiseImage.userId, promiseImage.promiseImageCommentType) + } + } +} diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDto.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDto.kt new file mode 100644 index 00000000..72837023 --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageDto.kt @@ -0,0 +1,15 @@ +package com.depromeet.whatnow.api.image.dto + +import com.depromeet.whatnow.domains.image.domain.PromiseImage + +data class PromiseImageDto( + val imageKey: String, + val imageUrl: String, + val uploadUserId: Long, +) { + companion object { + fun from(promiseImage: PromiseImage): PromiseImageDto { + return PromiseImageDto(promiseImage.imageKey, promiseImage.uri, promiseImage.userId) + } + } +} diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageResponse.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageResponse.kt new file mode 100644 index 00000000..ff8854df --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/dto/PromiseImageResponse.kt @@ -0,0 +1,11 @@ +package com.depromeet.whatnow.api.image.dto + +import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType +import io.swagger.v3.oas.annotations.media.Schema + +data class PromiseImageResponse( + @Schema(description = "조회한 유저의 약속 상태 (LATE=빨강, WAIT=파랑)", example = "LATE") + val promiseUserType: PromiseUserType, + @Schema(description = "약속에 참여한 유저들의 사진 목록 (LATE, WAIT로 구분. WAIT는 가장 최신 1개만 존재") + val promiseImages: MutableMap>, +) diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCase.kt new file mode 100644 index 00000000..73daaea4 --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCase.kt @@ -0,0 +1,58 @@ +package com.depromeet.whatnow.api.image.usecase + +import com.depromeet.whatnow.annotation.UseCase +import com.depromeet.whatnow.api.image.dto.PromiseImageDetailResponse +import com.depromeet.whatnow.api.image.dto.PromiseImageDto +import com.depromeet.whatnow.api.image.dto.PromiseImageResponse +import com.depromeet.whatnow.config.security.SecurityUtils +import com.depromeet.whatnow.domains.image.service.ImageDomainService +import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType +import com.depromeet.whatnow.domains.promiseuser.service.PromiseUserDomainService + +@UseCase +class PromiseImageReadUseCase( + val imageDomainService: ImageDomainService, + val promiseUserDomainService: PromiseUserDomainService, +) { + + fun getLateAndWaitImagesByPromiseId(promiseId: Long): PromiseImageResponse { + var userId = SecurityUtils.currentUserId + + // 사진을 조회하는 유저의 Late, Wait 상태를 조회하기 위해 + val promiseUser = promiseUserDomainService.findByPromiseIdAndUserId(promiseId, userId) + + val result: MutableMap> = mutableMapOf() + val promiseImages = imageDomainService.getImagesByPromiseId(promiseId) + + // LATE의 사진 + result[PromiseUserType.LATE] = ( + promiseImages.filter { promiseImage -> promiseUserDomainService.findByPromiseIdAndUserId(promiseId, promiseImage.userId).promiseUserType == PromiseUserType.LATE } + .sortedByDescending { promiseImage -> promiseImage.createdAt } + .map { promiseImage -> + PromiseImageDto(promiseImage.imageKey, promiseImage.uri, promiseImage.userId) + }.toList() + ) + + // WAIT의 사진 (가장 최신 1개) + result[PromiseUserType.WAIT] = ( + promiseImages.filter { promiseImage -> promiseUserDomainService.findByPromiseIdAndUserId(promiseId, promiseImage.userId).promiseUserType == PromiseUserType.WAIT } + .sortedByDescending { promiseImage -> promiseImage.createdAt } + ).firstOrNull()?.let { promiseImage -> + listOf( + PromiseImageDto(promiseImage.imageKey, promiseImage.uri, promiseImage.userId), + ) + } ?: listOf() + + return PromiseImageResponse(promiseUser.promiseUserType!!, result) + } + + fun getImagesByPromiseId(promiseId: Long): List { + return imageDomainService.getImagesByPromiseId(promiseId) + .map { PromiseImageDto.from(it) } + } + + fun getImageByImageKey(imageKey: String): PromiseImageDetailResponse { + return imageDomainService.getImageByImageKey(imageKey) + .let { PromiseImageDetailResponse.from(it) } + } +} diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/PromiseImageControllerTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt similarity index 92% rename from Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/PromiseImageControllerTest.kt rename to Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt index 88a26e1f..29e4d9e8 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/PromiseImageControllerTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt @@ -3,6 +3,7 @@ package com.depromeet.whatnow.api.image.controller import com.depromeet.whatnow.api.image.usecase.GetPresignedUrlUseCase import com.depromeet.whatnow.api.image.usecase.ImageCommentReadUseCase import com.depromeet.whatnow.api.image.usecase.ImageUploadSuccessUseCase +import com.depromeet.whatnow.api.image.usecase.PromiseImageReadUseCase import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType @@ -22,7 +23,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @WebMvcTest(ImageController::class) @ContextConfiguration(classes = [ImageController::class]) @AutoConfigureMockMvc(addFilters = false) -class PromiseImageControllerTest { +class ImageControllerTest { @MockBean lateinit var getPresignedUrlUseCase: GetPresignedUrlUseCase @@ -32,6 +33,9 @@ class PromiseImageControllerTest { @MockBean lateinit var imageCommentReadUseCase: ImageCommentReadUseCase + @MockBean + lateinit var promiseImageReadUseCase: PromiseImageReadUseCase + @Autowired lateinit var mockMvc: MockMvc @@ -46,7 +50,7 @@ class PromiseImageControllerTest { // when, then mockMvc.perform( - get("/v1/images/promises/{promiseId}", promiseId) + get("/v1/images/promises/{promiseId}/presigned-url", promiseId) .param("fileExtension", fileExtension), ) .andExpect(status().isOk) @@ -60,7 +64,7 @@ class PromiseImageControllerTest { // when, then mockMvc.perform( - get("/v1/images/users/me") + get("/v1/images/users/me/presigned-url") .param("fileExtension", fileExtension), ) .andExpect(status().isOk) diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt new file mode 100644 index 00000000..a484a5f9 --- /dev/null +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt @@ -0,0 +1,104 @@ +package com.depromeet.whatnow.api.image.usecase + +import com.depromeet.whatnow.domains.image.domain.PromiseImage +import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType +import com.depromeet.whatnow.domains.image.service.ImageDomainService +import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUser +import com.depromeet.whatnow.domains.promiseuser.domain.PromiseUserType +import com.depromeet.whatnow.domains.promiseuser.service.PromiseUserDomainService +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.given +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.context.SecurityContextHolder + +@ExtendWith(MockitoExtension::class) +class PromiseImageReadUseCaseTest { + @Mock + lateinit var imageDomainService: ImageDomainService + + @Mock + lateinit var promiseUserDomainService: PromiseUserDomainService + + @InjectMocks + lateinit var promiseImageReadUseCase: PromiseImageReadUseCase + + @BeforeEach + fun setup() { + val securityContext = SecurityContextHolder.createEmptyContext() + val authentication = UsernamePasswordAuthenticationToken("1", null, setOf(SimpleGrantedAuthority("ROLE_USER"))) + securityContext.authentication = authentication + SecurityContextHolder.setContext(securityContext) + } + + @Test + fun `약속 이미지를 모두 조회하면 WAIT, LATE로 구분되며, LATE는 가장최신 한게만 존재해야한다`() { + // given + val imageKey1 = "7e74f27e-0252-44cb-a2f2-17c1b5cce9c3" + val promiseImage1 = PromiseImage( + userId = 1, + promiseId = 1, + uri = "https://image/whatnow.kr/$imageKey1.jpg", + imageKey = imageKey1, + promiseImageCommentType = PromiseImageCommentType.DID_YOU_COME, + ) + val imageKey2 = "7e74f27e-0252-23ds-a2f2-58s1b5cce9c3" + val promiseImage2 = PromiseImage( + userId = 1, + promiseId = 1, + uri = "https://image/whatnow.kr/$imageKey2.jpg", + imageKey = imageKey2, + promiseImageCommentType = PromiseImageCommentType.DID_YOU_COME, + ) + val promiseUser1 = PromiseUser( + userId = 1, + promiseId = 1, + promiseUserType = PromiseUserType.WAIT, + ) + given(promiseUserDomainService.findByPromiseIdAndUserId(1, 1)).willReturn(promiseUser1) + + val imageKey3 = "7e74f27e-0252-23ds-a2f2-58s1b5cce9c3" + val promiseImage3 = PromiseImage( + userId = 2, + promiseId = 1, + uri = "https://image/whatnow.kr/$imageKey3.jpg", + imageKey = imageKey3, + promiseImageCommentType = PromiseImageCommentType.RUNNING, + ) + val imageKey4 = "7e74f27e-0252-23ds-a2f2-o50s1b5cce9c3" + val promiseImage4 = PromiseImage( + userId = 2, + promiseId = 1, + uri = "https://image/whatnow.kr/$imageKey4.jpg", + imageKey = imageKey4, + promiseImageCommentType = PromiseImageCommentType.RUNNING, + ) + val promiseUser2 = PromiseUser( + userId = 2, + promiseId = 1, + promiseUserType = PromiseUserType.LATE, + ) + given(promiseUserDomainService.findByPromiseIdAndUserId(1, 2)).willReturn(promiseUser2) + + given(imageDomainService.getImagesByPromiseId(1)).willReturn(listOf(promiseImage1, promiseImage2, promiseImage3, promiseImage4)) + + // when + val promiseImages = promiseImageReadUseCase.getLateAndWaitImagesByPromiseId(1) + + // then + // 이미지를 조회하는 사람의 타입이 같아야 한다 + assertEquals(promiseUser1.promiseUserType, promiseImages.promiseUserType) + + // 지각한 사람의 이미지는 모두 조회되어야 한다 + promiseImages.promiseImages[PromiseUserType.LATE]?.let { assertEquals(2, it.size) } + + // 기다리는 사람의 이미지는 가장 최신 한개만 조회되어야 한다 + promiseImages.promiseImages[PromiseUserType.WAIT]?.let { assertEquals(1, it.size) } + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt index 05c09ea2..7bde032b 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt @@ -11,4 +11,12 @@ class PromiseImageAdapter( fun save(promiseImage: PromiseImage): PromiseImage { return promiseImageRepository.save(promiseImage) } + + fun findAllByPromiseId(promiseId: Long): List { + return promiseImageRepository.findAllByPromiseId(promiseId) + } + + fun findByImageKey(imageKey: String): PromiseImage { + return promiseImageRepository.findByImageKey(imageKey) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt index 87f0d213..73cbed13 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt @@ -5,4 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface PromiseImageRepository : JpaRepository +interface PromiseImageRepository : JpaRepository { + fun findAllByPromiseId(promiseId: Long): List + fun findByImageKey(imageKey: String): PromiseImage +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt index 84b60438..783b9c6a 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt @@ -57,4 +57,13 @@ class ImageDomainService( } } } + + @Transactional(readOnly = true) + fun getImagesByPromiseId(promiseId: Long): List { + return promiseImageAdapter.findAllByPromiseId(promiseId) + } + + fun getImageByImageKey(imageKey: String): PromiseImage { + return promiseImageAdapter.findByImageKey(imageKey) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt index 0658a102..d47cc00c 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/promiseuser/service/PromiseUserDomainService.kt @@ -55,4 +55,8 @@ class PromiseUserDomainService( return distanceInMeters < RADIUS_WAIT_CONFIRM } + + fun findByPromiseIdAndUserId(promiseId: Long, userId: Long): PromiseUser { + return promiseUserAdaptor.findByPromiseIdAndUserId(promiseId, userId) + } } From d1474c1a27ba8c43981f84d99d1b458a22e74c81 Mon Sep 17 00:00:00 2001 From: BlackBean99 <54030889+BlackBean99@users.noreply.github.com> Date: Fri, 7 Jul 2023 23:08:03 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[DPMBE-104]=20=EC=95=BD=EC=86=8D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=EC=97=90=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=EB=A5=BC=20=EC=B6=94=EA=B0=80=EB=B0=8F=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EA=B8=B0=EC=A4=80=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4=20(#162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : promise Id, Address, Coordinate field 추가 * feat : PromiseFindDto 필드 추가 promiseId, address, coordinateVo * feat : 시간순 정렬 * feat : promiseId 로 조회하기 * feat : 정렬 기준 최신순변경 sorted -> sortedByDescending * test : 시간 순으로 정렬 변경 추가 * refactor : style 변경 --- .../promise/controller/PromiseController.kt | 11 +++- .../api/promise/dto/PromiseDetailDto.kt | 13 +++- .../whatnow/api/promise/dto/PromiseFindDto.kt | 15 +++-- .../api/promise/usecase/PromiseReadUseCase.kt | 66 ++++++++++--------- .../promise/usecase/PromiseReadUseCaseTest.kt | 64 +++++++++++++----- 5 files changed, 111 insertions(+), 58 deletions(-) diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/controller/PromiseController.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/controller/PromiseController.kt index 2d6042ce..97ec7121 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/controller/PromiseController.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/controller/PromiseController.kt @@ -42,7 +42,10 @@ class PromiseController( return promiseReadUseCase.findPromiseByUserIdSeparatedType() } - @Operation(summary = "월단위 약속 조회", description = "유저의 월간 약속 조회 (단, 예정된 약속과 지난 약속을 구분없이 조회), year-month 파라미터는 2021-01 이 형식입니다.") + @Operation( + summary = "월단위 약속 조회", + description = "유저의 월간 약속 조회 (단, 예정된 약속과 지난 약속을 구분없이 조회), year-month 파라미터는 2021-01 이 형식입니다.", + ) @GetMapping("/promises/users") fun findPromiseByUserAndYearMonth(@RequestParam(value = "year-month") yearMonth: YearMonth): List { return promiseReadUseCase.findPromiseByUserIdYearMonth(yearMonth) @@ -60,6 +63,12 @@ class PromiseController( return promiseReadUseCase.findPromiseActive(promiseId) } + @Operation(summary = "promiseId 로 약속 조회", description = "promiseId 로 약속 조회") + @GetMapping("/promises/{promise-id}") + fun findByPromiseId(@PathVariable(value = "promise-id") promiseId: Long): PromiseFindDto { + return promiseReadUseCase.findByPromiseId(promiseId) + } + @Operation(summary = "약속(promise) 생성", description = "약속을 생성합니다.") @PostMapping("/promises") fun createPromise(@RequestBody promiseRequest: PromiseRequest): PromiseDto { diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt index 1f88ca18..121d83d6 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt @@ -1,18 +1,22 @@ package com.depromeet.whatnow.api.promise.dto +import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.domains.promise.domain.Promise import java.time.LocalDateTime data class PromiseDetailDto( + val promiseId: Long, + val address: String, + val coordinateVo: CoordinateVo, val title: String, - val date: LocalDateTime, + val endTime: LocalDateTime, // 유저의 마지막 위치 리스트 val promiseUsers: List, // TODO : 약속 기록 사진 기능 추가시 함께 추가할게요. val promiseImageUrls: List?, val timeOverLocations: List, // TODO : highlight 기능 추가시 함께 추가할게요. ( 최대 3개 제한 ) -// val highlights: List, +// x val highlights: List, ) { companion object { fun of( @@ -22,8 +26,11 @@ data class PromiseDetailDto( timeOverLocations: List, ): PromiseDetailDto { return PromiseDetailDto( + promiseId = promise.id!!, + address = promise.meetPlace!!.address, + coordinateVo = promise.meetPlace!!.coordinate, title = promise.title, - date = promise.endTime, + endTime = promise.endTime, promiseUsers = promiseUsers, promiseImageUrls = promiseImageUrls, timeOverLocations = timeOverLocations, diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseFindDto.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseFindDto.kt index 3fdd3e60..1af1d467 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseFindDto.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseFindDto.kt @@ -1,12 +1,15 @@ package com.depromeet.whatnow.api.promise.dto +import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.common.vo.UserInfoVo import com.depromeet.whatnow.domains.promise.domain.Promise import java.time.LocalDateTime data class PromiseFindDto( - val title: String, + val promiseId: Long, val address: String, + val coordinateVo: CoordinateVo, + val title: String, val endTime: LocalDateTime, val users: List = mutableListOf(), ) { @@ -15,10 +18,12 @@ data class PromiseFindDto( val userValues = mutableListOf() userValues.addAll(users) return PromiseFindDto( - promise.title, - promise.meetPlace!!.address, - promise.endTime, - userValues, + promiseId = promise.id!!, + address = promise.meetPlace!!.address, + coordinateVo = promise.meetPlace!!.coordinate, + title = promise.title, + endTime = promise.endTime, + users = userValues, ) } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt index 7fa5f1a5..a5e64ce3 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt @@ -18,6 +18,7 @@ import com.depromeet.whatnow.domains.user.adapter.UserAdapter import com.depromeet.whatnow.domains.user.repository.UserRepository import java.time.LocalDateTime import java.time.YearMonth +import kotlin.collections.Map.Entry @UseCase class PromiseReadUseCase( @@ -34,18 +35,13 @@ class PromiseReadUseCase( */ fun findPromiseByUserIdSeparatedType(): Map> { val userId: Long = SecurityUtils.currentUserId - - // 내가 참여한 약속들(약속유저) val promiseUsers = promiseUserAdaptor.findByUserId(userId) val promiseIds = promiseUsers.map { it.promiseId } - // 내가 참여한 약속들 val promises = promiseAdaptor.queryPromises(promiseIds).associateBy { it.id } - // 내가 참여한 약속들에 참여한 친구들 val promiseUsersByPromiseId = promiseUserAdaptor.findByPromiseIds(promiseIds).groupBy { it.promiseId } val promiseSplitByPromiseTypeDto = mutableMapOf>() - for (promiseUser in promiseUsers) { - // 약속 하나씩 + promiseUsers.forEach() { promiseUser -> val promise = promises[promiseUser.promiseId] val eachPromiseUsers = promiseUsersByPromiseId[promiseUser.promiseId] ?: emptyList() val participant = getParticipantUserInfo(eachPromiseUsers) @@ -60,16 +56,20 @@ class PromiseReadUseCase( } PromiseType.DELETED -> { - // TODO - // Do nothing for deleted promises. + return@forEach } null -> { - // TODO + return@forEach } else -> { - // TODO + return@forEach } } + + val action: (Entry>) -> Unit = { (promiseType, promiseFindDtos) -> + promiseFindDtos.sortBy { it.endTime } + } + promiseSplitByPromiseTypeDto.forEach(action) } return promiseSplitByPromiseTypeDto } @@ -85,24 +85,11 @@ class PromiseReadUseCase( .map { promise -> // 약속에 참여한 유저들 val participants = getParticipantUserInfo(promiseUserAdaptor.findByPromiseId(promise.id!!)) - PromiseFindDto( - title = promise.title, - address = promise.meetPlace!!.address, - endTime = promise.endTime, + PromiseFindDto.of( + promise = promise, users = participants, ) - } - } - - fun findPromisesByUserId(userId: Long): List { - val promiseUsers = promiseUserAdaptor.findByUserId(userId) - return promiseUsers.map { promiseAdaptor.queryPromise(it.promiseId) } - } - - fun getParticipantUserInfo(promiseUsers: List): List { - val userIds = promiseUsers.map { it.userId } - val users = userRepository.findAllById(userIds) - return users.mapNotNull { UserInfoVo.from(it) } + }.sortedByDescending { it.endTime } } fun findPromiseDetailByStatus(promiseType: PromiseType): List { @@ -136,16 +123,16 @@ class PromiseReadUseCase( } result.add( - PromiseDetailDto( - title = promise.title, - date = promise.endTime, + PromiseDetailDto.of( + promise = promise, promiseUsers = promiseUserInfoVos, - promiseImageUrls = null, timeOverLocations = timeOverLocations, + promiseImageUrls = mutableListOf(), ), ) } - return result + + return result.sortedByDescending { it.endTime } } fun findPromiseActive(promiseId: Long): Boolean { @@ -155,4 +142,21 @@ class PromiseReadUseCase( val endTime = promise.endTime return now.isAfter(endTime.minusHours(1)) && now.isBefore(endTime.plusMinutes(30)) } + private fun getParticipantUserInfo(promiseUsers: List): List { + val userIds = promiseUsers.map { it.userId } + val users = userRepository.findAllById(userIds) + return users.mapNotNull { UserInfoVo.from(it) } + } + + private fun findPromisesByUserId(userId: Long): List { + val promiseUsers = promiseUserAdaptor.findByUserId(userId) + return promiseUsers.map { promiseAdaptor.queryPromise(it.promiseId) }.sortedByDescending { it.endTime } + } + + fun findByPromiseId(promiseId: Long): PromiseFindDto { + val promise = promiseAdaptor.queryPromise(promiseId) + val promiseUsers = promiseUserAdaptor.findByPromiseId(promiseId) + val participants = getParticipantUserInfo(promiseUsers) + return PromiseFindDto.of(promise, participants) + } } diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt index c1478a6a..acfc6c49 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt @@ -1,5 +1,7 @@ package com.depromeet.whatnow.api.promise.usecase +import com.depromeet.whatnow.common.vo.CoordinateVo +import com.depromeet.whatnow.common.vo.PlaceVo import com.depromeet.whatnow.domains.interaction.adapter.InteractionAdapter import com.depromeet.whatnow.domains.interaction.domain.Interaction import com.depromeet.whatnow.domains.interaction.domain.InteractionType @@ -21,7 +23,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.InjectMocks import org.mockito.Mock -import org.mockito.Mockito +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.junit.jupiter.MockitoExtension import org.springframework.security.authentication.UsernamePasswordAuthenticationToken @@ -74,12 +76,31 @@ class PromiseReadUseCaseTest { val promiseTime2 = LocalDateTime.now() val promiseUsers = listOf( PromiseUser(userId = 1, promiseId = 1, promiseUserType = WAIT), -// PromiseUser(userId = 2, promiseId = 1, promiseUserType = LATE), PromiseUser(userId = 1, promiseId = 2, promiseUserType = WAIT), ) val promises = listOf( - Promise(id = 1, title = "Promise 1", endTime = promiseTime1, mainUserId = 1), - Promise(id = 2, title = "Promise 2", endTime = promiseTime2, mainUserId = 2L), + Promise( + id = 1, + title = "Promise 1", + endTime = promiseTime1, + mainUserId = 1L, + meetPlace = PlaceVo( + CoordinateVo(352.1, 167.2), + "서울시 강남구", + ), + promiseType = PromiseType.BEFORE, + ), + Promise( + id = 2, + title = "Promise 2", + endTime = promiseTime2, + mainUserId = 2L, + meetPlace = PlaceVo( + CoordinateVo(123.4, 234.2), + "전라북도 남원시", + ), + promiseType = PromiseType.DELETED, + ), ) val users = listOf( User( @@ -99,17 +120,22 @@ class PromiseReadUseCaseTest { id = 2, ), ) - val interactions = listOf( + val interactionsPromise1 = listOf( Interaction(InteractionType.HEART, 1, 1, 242), Interaction(InteractionType.MUSIC, 1, 1, 1234), - Interaction(InteractionType.POOP, 1, 1, 12), - Interaction(InteractionType.STEP, 1, 1, 2934), ) - Mockito.`when`(promiseUserAdaptor.findByUserId(userId)).thenReturn(promiseUsers) - Mockito.`when`(promiseAdaptor.queryPromises(listOf(1, 2))).thenReturn(promises) - Mockito.`when`(userAdapter.queryUsers(listOf(1))).thenReturn(users) - Mockito.`when`(interactionAdapter.queryAllInteraction(1, 1)).thenReturn(interactions) + val interactionsPromise2 = listOf( + Interaction(InteractionType.POOP, 1, 2, 12), + Interaction(InteractionType.STEP, 1, 2, 2934), + ) + + `when`(promiseUserAdaptor.findByUserId(userId)).thenReturn(promiseUsers) + `when`(promiseAdaptor.queryPromises(listOf(1, 2))).thenReturn(promises) + `when`(userAdapter.queryUsers(listOf(1))).thenReturn(users) +// `when`(interactionAdapter.queryAllInteraction(1, 1)).thenReturn(interactions) + `when`(interactionAdapter.queryAllInteraction(1, 1)).thenReturn(interactionsPromise1) + `when`(interactionAdapter.queryAllInteraction(2, 1)).thenReturn(interactionsPromise2) // When val result = promiseReadUseCase.findPromiseDetailByStatus(PromiseType.BEFORE) @@ -117,15 +143,17 @@ class PromiseReadUseCaseTest { // Then Assertions.assertEquals(2, result.size) - Assertions.assertEquals("Promise 1", result[0].title) - Assertions.assertEquals(promiseTime1, result[0].date) - Assertions.assertEquals(1, result[0].promiseUsers.size) - - Assertions.assertEquals("Promise 2", result[1].title) - Assertions.assertEquals(promiseTime2, result[1].date) + Assertions.assertEquals("Promise 2", result[0].title) + Assertions.assertEquals(promiseTime1, result[1].endTime) Assertions.assertEquals(1, result[1].promiseUsers.size) + Assertions.assertEquals("Promise 1", result[1].title) + Assertions.assertEquals(promiseTime2, result[0].endTime) + Assertions.assertEquals(1, result[0].promiseUsers.size) + + // 약속 1번 + Assertions.assertEquals(1234, result[1].promiseUsers[0].interactions[0].count) + // 약속 2번 Assertions.assertEquals(2934, result[0].promiseUsers[0].interactions[0].count) - Assertions.assertEquals(1234, result[0].promiseUsers[0].interactions[1].count) } } From af55f68e4b3d979e72b0f2ab10e169bfbc711d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=8F=84=EB=AA=A8?= Date: Fri, 7 Jul 2023 23:29:06 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[DPMBE-76]=20=EC=82=AC=EC=A7=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4=20(S3=EC=97=90=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EB=8F=84=20=EA=B0=99=EC=9D=B4=20=EC=82=AD=EC=A0=9C)=20(#157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 사진 삭제 기능을 추가한다 (S3에 이미지도 같이 삭제) * remove: UserImageDeleteUseCase 삭제 (Service에 메서드만 남겨둠) * style: spotless --- .../api/image/controller/ImageController.kt | 17 ++++++-- .../image/usecase/GetPresignedUrlUseCase.kt | 8 ++-- .../usecase/ImageUploadSuccessUseCase.kt | 8 ++-- .../usecase/PromiseImageDeleteUseCase.kt | 15 +++++++ .../image/usecase/UserImageDeleteUseCase.kt | 15 +++++++ .../image/controller/ImageControllerTest.kt | 22 +++++----- .../usecase/GetPresignedUrlUseCaseTest.kt | 8 ++-- .../usecase/PromiseImageReadUseCaseTest.kt | 5 +++ .../PromiseImageUploadSuccessUseCaseTest.kt | 5 ++- .../image/adapter/PromiseImageAdapter.kt | 12 +++++- .../domains/image/adapter/UserImageAdapter.kt | 9 +++++ .../domains/image/domain/PromiseImage.kt | 21 +++++++++- .../whatnow/domains/image/domain/UserImage.kt | 25 +++++++++++- .../domains/image/exception/ImageErrorCode.kt | 18 +++++++-- .../PromiseImageNotFoundException.kt | 11 +++++ .../PromiseImageOwnershipMismatchException.kt | 11 +++++ .../exception/UserImageNotFoundException.kt | 11 +++++ .../UserImageOwnershipMismatchException.kt | 11 +++++ .../repository/PromiseImageRepository.kt | 4 +- .../image/repository/UserImageRepository.kt | 5 ++- .../image/service/ImageDomainService.kt | 22 ++++++++-- .../domainEvent/PromiseImageDeletedEvent.kt | 10 +++++ .../domainEvent/UserImageDeletedEvent.kt | 10 +++++ .../handler/ImageDeletedEventHandler.kt | 40 +++++++++++++++++++ .../adapter/PromisePromiseImageAdapterTest.kt | 4 +- .../service/PromiseImageDomainServiceTest.kt | 16 +++++--- ...> PromiseImageRegisterEventHandlerTest.kt} | 5 ++- ...oadPresignedUrlService.kt => S3Service.kt} | 29 +++++++++----- ...gnedUrlServiceTest.kt => S3ServiceTest.kt} | 10 ++--- 29 files changed, 325 insertions(+), 62 deletions(-) create mode 100644 Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageDeleteUseCase.kt create mode 100644 Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/UserImageDeleteUseCase.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageNotFoundException.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageOwnershipMismatchException.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageNotFoundException.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageOwnershipMismatchException.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/PromiseImageDeletedEvent.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserImageDeletedEvent.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/ImageDeletedEventHandler.kt rename Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/{PromisePromiseImageRegisterEventHandlerTest.kt => PromiseImageRegisterEventHandlerTest.kt} (90%) rename Whatnow-Infrastructure/src/main/kotlin/com/depromeet/whatnow/config/s3/{S3UploadPresignedUrlService.kt => S3Service.kt} (67%) rename Whatnow-Infrastructure/src/test/kotlin/com/depromeet/whatnow/config/s3/{S3UploadPresignedUrlServiceTest.kt => S3ServiceTest.kt} (86%) diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt index 3dea4031..79a3da52 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/controller/ImageController.kt @@ -7,12 +7,14 @@ import com.depromeet.whatnow.api.image.dto.PromiseImageResponse import com.depromeet.whatnow.api.image.usecase.GetPresignedUrlUseCase import com.depromeet.whatnow.api.image.usecase.ImageCommentReadUseCase import com.depromeet.whatnow.api.image.usecase.ImageUploadSuccessUseCase +import com.depromeet.whatnow.api.image.usecase.PromiseImageDeleteUseCase import com.depromeet.whatnow.api.image.usecase.PromiseImageReadUseCase import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping @@ -28,6 +30,7 @@ class ImageController( val successUseCase: ImageUploadSuccessUseCase, val imageCommentReadUseCase: ImageCommentReadUseCase, val promiseImageReadUseCase: PromiseImageReadUseCase, + val promiseImageDeleteUseCase: PromiseImageDeleteUseCase, ) { @Tag(name = "6-1 [약속 이미지]") @Operation(summary = "약속 이미지 업로드 Presigned URL 발급") @@ -45,9 +48,10 @@ class ImageController( fun promiseUploadImageSuccess( @PathVariable promiseId: Long, @PathVariable imageKey: String, + @RequestParam fileExtension: ImageFileExtension, @RequestParam promiseImageCommentType: PromiseImageCommentType, ) { - successUseCase.promiseUploadImageSuccess(promiseId, imageKey, promiseImageCommentType) + successUseCase.promiseUploadImageSuccess(promiseId, imageKey, fileExtension, promiseImageCommentType) } @Tag(name = "6-1 [약속 이미지]") @@ -71,6 +75,13 @@ class ImageController( return promiseImageReadUseCase.getImageByImageKey(imageKey) } + @Tag(name = "6-1 [약속 이미지]") + @Operation(summary = "이미지 키를 통해 약속 이미지를 삭제합니다.") + @DeleteMapping("/{imageKey}/promises/{promiseId}") + fun deletePromiseImage(@PathVariable promiseId: Long, @PathVariable imageKey: String) { + promiseImageDeleteUseCase.execute(promiseId, imageKey) + } + @Tag(name = "6-2 [유저 이미지]") @Operation(summary = "유저 프로필 이미지 업로드 Presigned URL 발급") @GetMapping("/users/me/presigned-url") @@ -83,7 +94,7 @@ class ImageController( @Tag(name = "6-2 [유저 이미지]") @Operation(summary = "유저 프로필 이미지 업로드 성공 요청") @PostMapping("/{imageKey}/users/me") - fun userUploadImageSuccess(@PathVariable imageKey: String) { - successUseCase.userUploadImageSuccess(imageKey) + fun userUploadImageSuccess(@PathVariable imageKey: String, @RequestParam fileExtension: ImageFileExtension) { + successUseCase.userUploadImageSuccess(imageKey, fileExtension) } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt index 59188731..fb8739a3 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt @@ -3,19 +3,19 @@ package com.depromeet.whatnow.api.image.usecase import com.depromeet.whatnow.annotation.UseCase import com.depromeet.whatnow.api.image.dto.ImageUrlResponse import com.depromeet.whatnow.config.s3.ImageFileExtension -import com.depromeet.whatnow.config.s3.S3UploadPresignedUrlService +import com.depromeet.whatnow.config.s3.S3Service import com.depromeet.whatnow.config.security.SecurityUtils @UseCase class GetPresignedUrlUseCase( - val presignedUrlService: S3UploadPresignedUrlService, + val presignedUrlService: S3Service, ) { fun forPromise(promiseId: Long, fileExtension: ImageFileExtension): ImageUrlResponse { - return ImageUrlResponse.from(presignedUrlService.forPromise(promiseId, fileExtension)) + return ImageUrlResponse.from(presignedUrlService.getPresignedUrlForPromise(promiseId, fileExtension)) } fun forUser(fileExtension: ImageFileExtension): ImageUrlResponse { val currentUserId = SecurityUtils.currentUserId - return ImageUrlResponse.from(presignedUrlService.forUser(currentUserId, fileExtension)) + return ImageUrlResponse.from(presignedUrlService.getPresignedUrlForUser(currentUserId, fileExtension)) } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt index 8ae6a897..0f4905b7 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/ImageUploadSuccessUseCase.kt @@ -1,6 +1,7 @@ package com.depromeet.whatnow.api.image.usecase import com.depromeet.whatnow.annotation.UseCase +import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.config.security.SecurityUtils import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.service.ImageDomainService @@ -12,14 +13,15 @@ class ImageUploadSuccessUseCase( fun promiseUploadImageSuccess( promiseId: Long, imageKey: String, + fileExtension: ImageFileExtension, promiseImageCommentType: PromiseImageCommentType, ) { val currentUserId: Long = SecurityUtils.currentUserId - imageDomainService.promiseImageUploadSuccess(currentUserId, promiseId, imageKey, promiseImageCommentType) + imageDomainService.promiseImageUploadSuccess(currentUserId, promiseId, imageKey, fileExtension, promiseImageCommentType) } - fun userUploadImageSuccess(imageKey: String) { + fun userUploadImageSuccess(imageKey: String, fileExtension: ImageFileExtension) { val currentUserId: Long = SecurityUtils.currentUserId - imageDomainService.userImageUploadSuccess(currentUserId, imageKey) + imageDomainService.userImageUploadSuccess(currentUserId, imageKey, fileExtension) } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageDeleteUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageDeleteUseCase.kt new file mode 100644 index 00000000..fa3344a8 --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageDeleteUseCase.kt @@ -0,0 +1,15 @@ +package com.depromeet.whatnow.api.image.usecase + +import com.depromeet.whatnow.annotation.UseCase +import com.depromeet.whatnow.config.security.SecurityUtils +import com.depromeet.whatnow.domains.image.service.ImageDomainService + +@UseCase +class PromiseImageDeleteUseCase( + val imageDomainService: ImageDomainService, +) { + fun execute(promiseId: Long, imageKey: String) { + val userId = SecurityUtils.currentUserId + imageDomainService.deleteForPromise(userId, promiseId, imageKey) + } +} diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/UserImageDeleteUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/UserImageDeleteUseCase.kt new file mode 100644 index 00000000..c1425cd9 --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/UserImageDeleteUseCase.kt @@ -0,0 +1,15 @@ +package com.depromeet.whatnow.api.image.usecase + +import com.depromeet.whatnow.annotation.UseCase +import com.depromeet.whatnow.config.security.SecurityUtils +import com.depromeet.whatnow.domains.image.service.ImageDomainService + +@UseCase +class UserImageDeleteUseCase( + val imageDomainService: ImageDomainService, +) { + fun execute(imageKey: String) { + val userId = SecurityUtils.currentUserId + imageDomainService.deleteForUser(userId, imageKey) + } +} diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt index 29e4d9e8..8400e222 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/controller/ImageControllerTest.kt @@ -3,17 +3,15 @@ package com.depromeet.whatnow.api.image.controller import com.depromeet.whatnow.api.image.usecase.GetPresignedUrlUseCase import com.depromeet.whatnow.api.image.usecase.ImageCommentReadUseCase import com.depromeet.whatnow.api.image.usecase.ImageUploadSuccessUseCase +import com.depromeet.whatnow.api.image.usecase.PromiseImageDeleteUseCase import com.depromeet.whatnow.api.image.usecase.PromiseImageReadUseCase -import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType -import com.fasterxml.jackson.databind.ObjectMapper import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.http.MediaType import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders @@ -36,11 +34,11 @@ class ImageControllerTest { @MockBean lateinit var promiseImageReadUseCase: PromiseImageReadUseCase - @Autowired - lateinit var mockMvc: MockMvc + @MockBean + lateinit var promiseImageDeleteUseCase: PromiseImageDeleteUseCase @Autowired - lateinit var objectMapper: ObjectMapper + lateinit var mockMvc: MockMvc @Test fun `약속 이미지 presignedUrl 요청에 성공하면 200을 응답한다`() { @@ -76,15 +74,13 @@ class ImageControllerTest { // given val promiseId = 1 val imageKey = "imageKey" + val fileExtension = ImageFileExtension.JPEG.name val promiseImageCommentType = PromiseImageCommentType.SORRY_LATE - val userLocation = CoordinateVo(127.3, 23.0) - // when, then mockMvc.perform( MockMvcRequestBuilders.post("/v1/images/{imageKey}/promises/{promiseId}", imageKey, promiseId) - .param("promiseImageCommentType", promiseImageCommentType.name) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(userLocation)), + .param("fileExtension", fileExtension) + .param("promiseImageCommentType", promiseImageCommentType.name), ) .andExpect(status().isOk) .andDo { print(it) } @@ -94,10 +90,12 @@ class ImageControllerTest { fun `유저 프로필 업로드 성공 요청에 정상적으로 200을 반환한다`() { // given val imageKey = "imageKey" + val fileExtension = ImageFileExtension.JPEG.name // when, then mockMvc.perform( - MockMvcRequestBuilders.post("/v1/images/{imageKey}/users/me", imageKey), + MockMvcRequestBuilders.post("/v1/images/{imageKey}/users/me", imageKey) + .param("fileExtension", fileExtension), ) .andExpect(status().isOk) .andDo { print(it) } diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCaseTest.kt index 053e61f8..8394e970 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCaseTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCaseTest.kt @@ -2,7 +2,7 @@ package com.depromeet.whatnow.api.image.usecase import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.config.s3.ImageUrlDto -import com.depromeet.whatnow.config.s3.S3UploadPresignedUrlService +import com.depromeet.whatnow.config.s3.S3Service import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -19,7 +19,7 @@ import org.springframework.security.test.context.support.WithMockUser @ExtendWith(MockitoExtension::class) class GetPresignedUrlUseCaseTest { @Mock - lateinit var presignedUrlService: S3UploadPresignedUrlService + lateinit var presignedUrlService: S3Service @InjectMocks lateinit var getPresignedUrlUseCase: GetPresignedUrlUseCase @@ -35,7 +35,7 @@ class GetPresignedUrlUseCaseTest { @Test fun `약속 이미지 PresignUrl 을 요청하면 url 을 반환한다`() { // given - given(presignedUrlService.forPromise(1, ImageFileExtension.JPEG)).willReturn( + given(presignedUrlService.getPresignedUrlForPromise(1, ImageFileExtension.JPEG)).willReturn( ImageUrlDto( url = "https://whatnow.kr/1.jpg", key = "1.jpg", @@ -53,7 +53,7 @@ class GetPresignedUrlUseCaseTest { @WithMockUser(username = "1") fun `유저 프로필 PresignUrl 을 요청하면 url 을 반환한다`() { // given - given(presignedUrlService.forUser(1, ImageFileExtension.JPEG)).willReturn( + given(presignedUrlService.getPresignedUrlForUser(1, ImageFileExtension.JPEG)).willReturn( ImageUrlDto( url = "https://whatnow.kr/1.jpg", key = "1.jpg", diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt index a484a5f9..ed58718a 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageReadUseCaseTest.kt @@ -1,5 +1,6 @@ package com.depromeet.whatnow.api.image.usecase +import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImage import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.service.ImageDomainService @@ -46,6 +47,7 @@ class PromiseImageReadUseCaseTest { promiseId = 1, uri = "https://image/whatnow.kr/$imageKey1.jpg", imageKey = imageKey1, + fileExtension = ImageFileExtension.JPG, promiseImageCommentType = PromiseImageCommentType.DID_YOU_COME, ) val imageKey2 = "7e74f27e-0252-23ds-a2f2-58s1b5cce9c3" @@ -54,6 +56,7 @@ class PromiseImageReadUseCaseTest { promiseId = 1, uri = "https://image/whatnow.kr/$imageKey2.jpg", imageKey = imageKey2, + fileExtension = ImageFileExtension.JPG, promiseImageCommentType = PromiseImageCommentType.DID_YOU_COME, ) val promiseUser1 = PromiseUser( @@ -69,6 +72,7 @@ class PromiseImageReadUseCaseTest { promiseId = 1, uri = "https://image/whatnow.kr/$imageKey3.jpg", imageKey = imageKey3, + fileExtension = ImageFileExtension.JPG, promiseImageCommentType = PromiseImageCommentType.RUNNING, ) val imageKey4 = "7e74f27e-0252-23ds-a2f2-o50s1b5cce9c3" @@ -77,6 +81,7 @@ class PromiseImageReadUseCaseTest { promiseId = 1, uri = "https://image/whatnow.kr/$imageKey4.jpg", imageKey = imageKey4, + fileExtension = ImageFileExtension.JPG, promiseImageCommentType = PromiseImageCommentType.RUNNING, ) val promiseUser2 = PromiseUser( diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt index 7892e2a9..075898ec 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/image/usecase/PromiseImageUploadSuccessUseCaseTest.kt @@ -1,5 +1,6 @@ package com.depromeet.whatnow.api.image.usecase +import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.service.ImageDomainService import org.assertj.core.api.Assertions.assertThatCode @@ -36,7 +37,7 @@ class PromiseImageUploadSuccessUseCaseTest { // then assertThatCode { - imageUploadSuccessUseCase.promiseUploadImageSuccess(1, "imageKey", PromiseImageCommentType.SORRY_LATE) + imageUploadSuccessUseCase.promiseUploadImageSuccess(1, "imageKey", ImageFileExtension.JPG, PromiseImageCommentType.SORRY_LATE) }.doesNotThrowAnyException() } @@ -48,7 +49,7 @@ class PromiseImageUploadSuccessUseCaseTest { // then assertThatCode { - imageUploadSuccessUseCase.userUploadImageSuccess("imageKey") + imageUploadSuccessUseCase.userUploadImageSuccess("imageKey", ImageFileExtension.JPG) }.doesNotThrowAnyException() } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt index 7bde032b..6d1286c8 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/PromiseImageAdapter.kt @@ -2,6 +2,7 @@ package com.depromeet.whatnow.domains.image.adapter import com.depromeet.whatnow.annotation.Adapter import com.depromeet.whatnow.domains.image.domain.PromiseImage +import com.depromeet.whatnow.domains.image.exception.PromiseImageNotFoundException import com.depromeet.whatnow.domains.image.repository.PromiseImageRepository @Adapter @@ -17,6 +18,15 @@ class PromiseImageAdapter( } fun findByImageKey(imageKey: String): PromiseImage { - return promiseImageRepository.findByImageKey(imageKey) + return promiseImageRepository.findByImageKey(imageKey) ?: run { throw PromiseImageNotFoundException.EXCEPTION } + } + + fun deleteByImageKeyAndPromiseId(imageKey: String, promiseId: Long) { + promiseImageRepository.deleteByPromiseIdAndImageKey(promiseId, imageKey) + } + + fun findByPromiseIdAndImageKey(promiseId: Long, imageKey: String): PromiseImage { + return promiseImageRepository.findByPromiseIdAndImageKey(promiseId, imageKey) + ?: run { throw PromiseImageNotFoundException.EXCEPTION } } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt index ac0480ae..fd5141ec 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt @@ -2,6 +2,7 @@ package com.depromeet.whatnow.domains.image.adapter import com.depromeet.whatnow.annotation.Adapter import com.depromeet.whatnow.domains.image.domain.UserImage +import com.depromeet.whatnow.domains.image.exception.UserImageNotFoundException import com.depromeet.whatnow.domains.image.repository.UserImageRepository @Adapter @@ -11,4 +12,12 @@ class UserImageAdapter( fun save(userImage: UserImage): UserImage { return userImageRepository.save(userImage) } + + fun findByUserIdAndImageKey(userId: Long, imageKey: String): UserImage { + return userImageRepository.findByUserIdAndImageKey(userId, imageKey) ?: run { throw UserImageNotFoundException.EXCEPTION } + } + + fun deleteByImageKeyAndUserId(imageKey: String, userId: Long) { + return userImageRepository.deleteByImageKeyAndUserId(imageKey, userId) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt index 3c8410a3..bac5de2c 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/PromiseImage.kt @@ -2,6 +2,9 @@ package com.depromeet.whatnow.domains.image.domain import com.depromeet.whatnow.common.BaseTimeEntity import com.depromeet.whatnow.common.aop.event.Events +import com.depromeet.whatnow.config.s3.ImageFileExtension +import com.depromeet.whatnow.domains.image.exception.PromiseImageOwnershipMismatchException +import com.depromeet.whatnow.events.domainEvent.PromiseImageDeletedEvent import com.depromeet.whatnow.events.domainEvent.PromiseImageRegisterEvent import javax.persistence.Column import javax.persistence.Entity @@ -11,6 +14,7 @@ import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id import javax.persistence.PostPersist +import javax.persistence.PostRemove import javax.persistence.Table @Entity @@ -24,6 +28,9 @@ class PromiseImage( var imageKey: String, + @Enumerated(EnumType.STRING) + var fileExtension: ImageFileExtension, + @Enumerated(EnumType.STRING) var promiseImageCommentType: PromiseImageCommentType, @@ -38,9 +45,10 @@ class PromiseImage( promiseId: Long, uri: String, imageKey: String, + fileExtension: ImageFileExtension, promiseImageCommentType: PromiseImageCommentType, ): PromiseImage { - return PromiseImage(userId, promiseId, uri, imageKey, promiseImageCommentType) + return PromiseImage(userId, promiseId, uri, imageKey, fileExtension, promiseImageCommentType) } } @@ -48,4 +56,15 @@ class PromiseImage( fun createImageEvent() { Events.raise(PromiseImageRegisterEvent(userId, promiseId)) } + + fun validateOwnership(userId: Long) { + if (this.userId != userId) { + throw PromiseImageOwnershipMismatchException.EXCEPTION + } + } + + @PostRemove + fun deletePromiseImage() { + Events.raise(PromiseImageDeletedEvent(promiseId, imageKey, fileExtension)) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/UserImage.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/UserImage.kt index 425279d9..a4ceb0e3 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/UserImage.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/domain/UserImage.kt @@ -1,11 +1,18 @@ package com.depromeet.whatnow.domains.image.domain import com.depromeet.whatnow.common.BaseTimeEntity +import com.depromeet.whatnow.common.aop.event.Events +import com.depromeet.whatnow.config.s3.ImageFileExtension +import com.depromeet.whatnow.domains.image.exception.UserImageOwnershipMismatchException +import com.depromeet.whatnow.events.domainEvent.UserImageDeletedEvent import javax.persistence.Column import javax.persistence.Entity +import javax.persistence.EnumType +import javax.persistence.Enumerated import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id +import javax.persistence.PostRemove import javax.persistence.Table @Entity @@ -17,14 +24,28 @@ class UserImage( var imageKey: String, + @Enumerated(EnumType.STRING) + var fileExtension: ImageFileExtension, + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_image_id") val id: Long? = null, ) : BaseTimeEntity() { companion object { - fun of(userId: Long, uri: String, imageKey: String): UserImage { - return UserImage(userId, uri, imageKey) + fun of(userId: Long, uri: String, imageKey: String, fileExtension: ImageFileExtension): UserImage { + return UserImage(userId, uri, imageKey, fileExtension) + } + } + + fun validateOwnership(userId: Long) { + if (this.userId != userId) { + throw UserImageOwnershipMismatchException.EXCEPTION } } + + @PostRemove + fun deleteUserImage() { + Events.raise(UserImageDeletedEvent(userId, imageKey, fileExtension)) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/ImageErrorCode.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/ImageErrorCode.kt index 64f9f326..a28159b9 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/ImageErrorCode.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/ImageErrorCode.kt @@ -8,13 +8,25 @@ import java.util.Objects enum class ImageErrorCode(val status: Int, val code: String, val reason: String) : BaseErrorCode { @ExplainError("트래킹이 시작 전에 이미지를 업로드하는 경우") - UPLOAD_BEFORE_TRACKING(BAD_REQUEST, "PICTURE_400_1", "트래킹 시작전에는 이미지를 업로드 할 수 없습니다."), + UPLOAD_BEFORE_TRACKING(BAD_REQUEST, "IMAGE_400_1", "트래킹 시작전에는 이미지를 업로드 할 수 없습니다."), @ExplainError("약속 참여에 취소한 유저가 업로드 하는 경우") - CANCELLED_USER_UPLOAD(BAD_REQUEST, "PICTURE_400_2", "약속 취소한 유저는 이미지를 업로드 할 수 없습니다."), + CANCELLED_USER_UPLOAD(BAD_REQUEST, "IMAGE_400_2", "약속 취소한 유저는 이미지를 업로드 할 수 없습니다."), @ExplainError("유저의 상태와 일치하는 코멘트가 아닐 경우") - INVALID_COMMENT_TYPE(BAD_REQUEST, "PICTURE_400_3", "유저의 상태와 일치하는 코멘트가 아닙니다."), + INVALID_COMMENT_TYPE(BAD_REQUEST, "IMAGE_400_3", "유저의 상태와 일치하는 코멘트가 아닙니다."), + + @ExplainError("약속 이미지를 찾지 못했을 경우") + PROMISE_IMAGE_NOT_FOUND(BAD_REQUEST, "IMAGE_400_4", "약속 이미지를 찾지 못했습니다."), + + @ExplainError("약속 이미지를 삭제하려는 유저와 이미지를 업로드한 유저가 다를 경우") + PROMISE_IMAGE_OWNERSHIP_MISMATCH(BAD_REQUEST, "IMAGE_400_5", "삭제하려는 유저가 약속 이미지를 업로드한 유저가 아닙니다."), + + @ExplainError("유저 이미지를 찾지 못했을 경우") + USER_IMAGE_NOT_FOUND(BAD_REQUEST, "IMAGE_400_6", "유저 이미지를 찾지 못했습니다."), + + @ExplainError("유저 이미지를 삭제하려는 유저와 이미지를 업로드한 유저가 다를 경우") + USER_IMAGE_OWNERSHIP_MISMATCH(BAD_REQUEST, "IMAGE_400_7", "삭제하려는 유저가 유저 이미지를 업로드한 유저가 아닙니다."), ; override val errorReason: ErrorReason diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageNotFoundException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageNotFoundException.kt new file mode 100644 index 00000000..9c2791ce --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageNotFoundException.kt @@ -0,0 +1,11 @@ +package com.depromeet.whatnow.domains.image.exception + +import com.depromeet.whatnow.exception.WhatnowCodeException + +class PromiseImageNotFoundException : WhatnowCodeException( + ImageErrorCode.PROMISE_IMAGE_NOT_FOUND, +) { + companion object { + val EXCEPTION: WhatnowCodeException = PromiseImageNotFoundException() + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageOwnershipMismatchException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageOwnershipMismatchException.kt new file mode 100644 index 00000000..6bb035b7 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/PromiseImageOwnershipMismatchException.kt @@ -0,0 +1,11 @@ +package com.depromeet.whatnow.domains.image.exception + +import com.depromeet.whatnow.exception.WhatnowCodeException + +class PromiseImageOwnershipMismatchException : WhatnowCodeException( + ImageErrorCode.PROMISE_IMAGE_OWNERSHIP_MISMATCH, +) { + companion object { + val EXCEPTION: WhatnowCodeException = PromiseImageOwnershipMismatchException() + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageNotFoundException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageNotFoundException.kt new file mode 100644 index 00000000..3917f6cc --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageNotFoundException.kt @@ -0,0 +1,11 @@ +package com.depromeet.whatnow.domains.image.exception + +import com.depromeet.whatnow.exception.WhatnowCodeException + +class UserImageNotFoundException : WhatnowCodeException( + ImageErrorCode.USER_IMAGE_NOT_FOUND, +) { + companion object { + val EXCEPTION: WhatnowCodeException = UserImageNotFoundException() + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageOwnershipMismatchException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageOwnershipMismatchException.kt new file mode 100644 index 00000000..f595a4d6 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/exception/UserImageOwnershipMismatchException.kt @@ -0,0 +1,11 @@ +package com.depromeet.whatnow.domains.image.exception + +import com.depromeet.whatnow.exception.WhatnowCodeException + +class UserImageOwnershipMismatchException : WhatnowCodeException( + ImageErrorCode.USER_IMAGE_OWNERSHIP_MISMATCH, +) { + companion object { + val EXCEPTION: WhatnowCodeException = UserImageOwnershipMismatchException() + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt index 73cbed13..e46085ec 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/PromiseImageRepository.kt @@ -7,5 +7,7 @@ import org.springframework.stereotype.Repository @Repository interface PromiseImageRepository : JpaRepository { fun findAllByPromiseId(promiseId: Long): List - fun findByImageKey(imageKey: String): PromiseImage + fun findByImageKey(imageKey: String): PromiseImage? + fun findByPromiseIdAndImageKey(promiseId: Long, imageKey: String): PromiseImage? + fun deleteByPromiseIdAndImageKey(promiseId: Long, imageKey: String) } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt index a2c86e52..9378b40f 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt @@ -5,4 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @Repository -interface UserImageRepository : JpaRepository +interface UserImageRepository : JpaRepository { + fun findByUserIdAndImageKey(userId: Long, imageKey: String): UserImage? + fun deleteByImageKeyAndUserId(imageKey: String, userId: Long) +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt index 783b9c6a..bf46ccfd 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/service/ImageDomainService.kt @@ -1,5 +1,6 @@ package com.depromeet.whatnow.domains.image.service +import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.consts.IMAGE_DOMAIN import com.depromeet.whatnow.domains.image.adapter.PromiseImageAdapter import com.depromeet.whatnow.domains.image.adapter.UserImageAdapter @@ -29,6 +30,7 @@ class ImageDomainService( userId: Long, promiseId: Long, imageKey: String, + fileExtension: ImageFileExtension, promiseImageCommentType: PromiseImageCommentType, ) { val promiseUser = promiseUserAdapter.findByPromiseIdAndUserId(promiseId, userId) @@ -36,14 +38,14 @@ class ImageDomainService( val imageUrl = IMAGE_DOMAIN + "/" + springEnvironmentHelper.activeProfile + "/" + "promise/$promiseId/$imageKey" promiseImageAdapter.save( - PromiseImage.of(promiseId, userId, imageUrl, imageKey, promiseImageCommentType), + PromiseImage.of(promiseId, userId, imageUrl, imageKey, fileExtension, promiseImageCommentType), ) } @Transactional - fun userImageUploadSuccess(userId: Long, imageKey: String) { + fun userImageUploadSuccess(userId: Long, imageKey: String, fileExtension: ImageFileExtension) { val imageUrl = IMAGE_DOMAIN + "/" + springEnvironmentHelper.activeProfile + "/" + "user/$userId/$imageKey" - userImageAdapter.save(UserImage.of(userId, imageUrl, imageKey)) + userImageAdapter.save(UserImage.of(userId, imageUrl, imageKey, fileExtension)) } private fun validatePromiseUserType(promiseUserType: PromiseUserType, promiseImageCommentType: PromiseImageCommentType) { @@ -66,4 +68,18 @@ class ImageDomainService( fun getImageByImageKey(imageKey: String): PromiseImage { return promiseImageAdapter.findByImageKey(imageKey) } + + @Transactional + fun deleteForPromise(userId: Long, promiseId: Long, imageKey: String) { + val promiseImage = promiseImageAdapter.findByPromiseIdAndImageKey(promiseId, imageKey) + promiseImage.validateOwnership(userId) + promiseImageAdapter.deleteByImageKeyAndPromiseId(imageKey, promiseId) + } + + @Transactional + fun deleteForUser(userId: Long, imageKey: String) { + val userImage = userImageAdapter.findByUserIdAndImageKey(userId, imageKey) + userImage.validateOwnership(userId) + userImageAdapter.deleteByImageKeyAndUserId(imageKey, userId) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/PromiseImageDeletedEvent.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/PromiseImageDeletedEvent.kt new file mode 100644 index 00000000..35c95f3a --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/PromiseImageDeletedEvent.kt @@ -0,0 +1,10 @@ +package com.depromeet.whatnow.events.domainEvent + +import com.depromeet.whatnow.common.aop.event.DomainEvent +import com.depromeet.whatnow.config.s3.ImageFileExtension + +class PromiseImageDeletedEvent( + val promiseId: Long, + val imageKey: String, + val imageFileExtension: ImageFileExtension, +) : DomainEvent() diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserImageDeletedEvent.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserImageDeletedEvent.kt new file mode 100644 index 00000000..e2709387 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserImageDeletedEvent.kt @@ -0,0 +1,10 @@ +package com.depromeet.whatnow.events.domainEvent + +import com.depromeet.whatnow.common.aop.event.DomainEvent +import com.depromeet.whatnow.config.s3.ImageFileExtension + +class UserImageDeletedEvent( + val userId: Long, + val imageKey: String, + val imageFileExtension: ImageFileExtension, +) : DomainEvent() diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/ImageDeletedEventHandler.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/ImageDeletedEventHandler.kt new file mode 100644 index 00000000..701de6d5 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/ImageDeletedEventHandler.kt @@ -0,0 +1,40 @@ +package com.depromeet.whatnow.events.handler + +import com.depromeet.whatnow.annotation.Handler +import com.depromeet.whatnow.config.s3.S3Service +import com.depromeet.whatnow.events.domainEvent.PromiseImageDeletedEvent +import com.depromeet.whatnow.events.domainEvent.UserImageDeletedEvent +import mu.KLogger +import mu.KotlinLogging +import org.springframework.scheduling.annotation.Async +import org.springframework.transaction.event.TransactionPhase +import org.springframework.transaction.event.TransactionalEventListener + +@Handler +class ImageDeletedEventHandler( + val s3Service: S3Service, +) { + val logger: KLogger = KotlinLogging.logger {} + + @Async + @TransactionalEventListener(classes = [UserImageDeletedEvent::class], phase = TransactionPhase.AFTER_COMMIT) + fun handleUserImageDeletedEvent(userImageDeletedEvent: UserImageDeletedEvent) { + val imageKey = userImageDeletedEvent.imageKey + val userId = userImageDeletedEvent.userId + val imageFileExtension = userImageDeletedEvent.imageFileExtension + logger.info { "UserImageDeletedEvent 이벤트 수신 imageKey=$imageKey, userId=$userId, fileExtension=$imageFileExtension" } + + s3Service.deleteForUser(userId, imageKey, imageFileExtension) + } + + @Async + @TransactionalEventListener(classes = [PromiseImageDeletedEvent::class], phase = TransactionPhase.AFTER_COMMIT) + fun handlePromiseImageDeletedEvent(promiseImageDeletedEvent: PromiseImageDeletedEvent) { + val imageKey = promiseImageDeletedEvent.imageKey + val promiseId = promiseImageDeletedEvent.promiseId + val imageFileExtension = promiseImageDeletedEvent.imageFileExtension + logger.info { "promiseImageDeletedEvent 이벤트 수신 imageKey=$imageKey, promiseId=$promiseId, fileExtension=$imageFileExtension" } + + s3Service.deleteForPromise(promiseId, imageKey, imageFileExtension) + } +} diff --git a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt index a75c84cf..14e476f2 100644 --- a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt +++ b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/adapter/PromisePromiseImageAdapterTest.kt @@ -1,5 +1,6 @@ package com.depromeet.whatnow.domains.image.adapter +import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImage import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.repository.PromiseImageRepository @@ -22,7 +23,8 @@ class PromisePromiseImageAdapterTest { @Test fun `약속 이미지 저장 시 정상적으로 저장된다`() { - val promiseImage = PromiseImage.of(1, 1, "imageUri", "imageKey", PromiseImageCommentType.RUNNING) + // given + val promiseImage = PromiseImage.of(1, 1, "imageUri", "imageKey", ImageFileExtension.JPEG, PromiseImageCommentType.RUNNING) given(promiseImageRepository.save(Mockito.any(PromiseImage::class.java))) .willReturn(promiseImage) diff --git a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt index e5c6345a..905d9b8d 100644 --- a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt +++ b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/domains/image/service/PromiseImageDomainServiceTest.kt @@ -1,6 +1,7 @@ package com.depromeet.whatnow.domains.image.service import com.depromeet.whatnow.common.vo.CoordinateVo +import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.adapter.PromiseImageAdapter import com.depromeet.whatnow.domains.image.adapter.UserImageAdapter import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType @@ -50,12 +51,13 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.LATE, ) + val fileExtension = ImageFileExtension.JPEG given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatCode { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", fileExtension, PromiseImageCommentType.RUNNING) }.doesNotThrowAnyException() } @@ -68,12 +70,13 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.READY, ) + val fileExtension = ImageFileExtension.JPEG given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", fileExtension, PromiseImageCommentType.RUNNING) }.isInstanceOf(UploadBeforeTrackingException::class.java) } @@ -86,12 +89,13 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.CANCEL, ) + val fileExtension = ImageFileExtension.JPEG given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", fileExtension, PromiseImageCommentType.RUNNING) }.isInstanceOf(CancelledUserUploadException::class.java) } @@ -104,12 +108,13 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.LATE, ) + val fileExtension = ImageFileExtension.JPEG given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.DID_YOU_COME) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", fileExtension, PromiseImageCommentType.DID_YOU_COME) }.isInstanceOf(InvalidCommentTypeException::class.java) } @@ -122,12 +127,13 @@ class PromiseImageDomainServiceTest { userLocation = CoordinateVo(1.0, 1.0), promiseUserType = PromiseUserType.WAIT, ) + val fileExtension = ImageFileExtension.JPEG given(promiseUserAdapter.findByPromiseIdAndUserId(anyLong(), anyLong())) .willReturn(promiseUser) // when, then Assertions.assertThatThrownBy { - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.WAIT_A_BIT) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", fileExtension, PromiseImageCommentType.WAIT_A_BIT) }.isInstanceOf(InvalidCommentTypeException::class.java) } } diff --git a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromisePromiseImageRegisterEventHandlerTest.kt b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromiseImageRegisterEventHandlerTest.kt similarity index 90% rename from Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromisePromiseImageRegisterEventHandlerTest.kt rename to Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromiseImageRegisterEventHandlerTest.kt index fc140277..48882d6b 100644 --- a/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromisePromiseImageRegisterEventHandlerTest.kt +++ b/Whatnow-Domain/src/test/kotlin/com/depromeet/whatnow/events/handler/PromiseImageRegisterEventHandlerTest.kt @@ -2,6 +2,7 @@ package com.depromeet.whatnow.events.handler import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.config.DomainIntegrateSpringBootTest +import com.depromeet.whatnow.config.s3.ImageFileExtension import com.depromeet.whatnow.domains.image.domain.PromiseImageCommentType import com.depromeet.whatnow.domains.image.service.ImageDomainService import com.depromeet.whatnow.domains.promiseuser.adaptor.PromiseUserAdaptor @@ -17,7 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.mock.mockito.MockBean @DomainIntegrateSpringBootTest -class PromisePromiseImageRegisterEventHandlerTest { +class PromiseImageRegisterEventHandlerTest { @Autowired lateinit var imageDomainService: ImageDomainService @@ -37,7 +38,7 @@ class PromisePromiseImageRegisterEventHandlerTest { given(promiseUserAdaptor.findByPromiseIdAndUserId(1, 1)).willReturn(promiseUser) // when - imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", PromiseImageCommentType.RUNNING) + imageDomainService.promiseImageUploadSuccess(1, 1, "imageKey", ImageFileExtension.JPEG, PromiseImageCommentType.RUNNING) // then then(imageRegisterEventHandler).should(Mockito.times(1)).handleRegisterPictureEvent(any()) diff --git a/Whatnow-Infrastructure/src/main/kotlin/com/depromeet/whatnow/config/s3/S3UploadPresignedUrlService.kt b/Whatnow-Infrastructure/src/main/kotlin/com/depromeet/whatnow/config/s3/S3Service.kt similarity index 67% rename from Whatnow-Infrastructure/src/main/kotlin/com/depromeet/whatnow/config/s3/S3UploadPresignedUrlService.kt rename to Whatnow-Infrastructure/src/main/kotlin/com/depromeet/whatnow/config/s3/S3Service.kt index f7a72ffd..52657717 100644 --- a/Whatnow-Infrastructure/src/main/kotlin/com/depromeet/whatnow/config/s3/S3UploadPresignedUrlService.kt +++ b/Whatnow-Infrastructure/src/main/kotlin/com/depromeet/whatnow/config/s3/S3Service.kt @@ -4,37 +4,48 @@ import com.amazonaws.HttpMethod import com.amazonaws.services.s3.AmazonS3 import com.amazonaws.services.s3.Headers import com.amazonaws.services.s3.model.CannedAccessControlList +import com.amazonaws.services.s3.model.DeleteObjectRequest import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest import com.depromeet.whatnow.helper.SpringEnvironmentHelper import org.springframework.stereotype.Service import java.util.* @Service -class S3UploadPresignedUrlService( +class S3Service( val amazonS3: AmazonS3, s3Properties: S3Properties, val springEnvironmentHelper: SpringEnvironmentHelper, ) { val s3Secret: S3Properties.S3Secret = s3Properties.s3 - fun forPromise(promiseId: Long, fileExtension: ImageFileExtension): ImageUrlDto { - val uuid = UUID.randomUUID().toString() - var fileName = getForPromiseFimeName(uuid, promiseId, fileExtension) + fun getPresignedUrlForPromise(promiseId: Long, fileExtension: ImageFileExtension): ImageUrlDto { + val imageKey = UUID.randomUUID().toString() + var fileName = getForPromiseFimeName(imageKey, promiseId, fileExtension) val generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(s3Secret.bucket, fileName, fileExtension.uploadExtension) val generatePresignedUrl = amazonS3.generatePresignedUrl(generatePresignedUrlRequest) - return ImageUrlDto(generatePresignedUrl.toString(), uuid) + return ImageUrlDto(generatePresignedUrl.toString(), imageKey) } - fun forUser(userId: Long, fileExtension: ImageFileExtension): ImageUrlDto { - val uuid = UUID.randomUUID().toString() - var fileName = getForUserFimeName(uuid, userId, fileExtension) + fun getPresignedUrlForUser(userId: Long, fileExtension: ImageFileExtension): ImageUrlDto { + val imageKey = UUID.randomUUID().toString() + var fileName = getForUserFimeName(imageKey, userId, fileExtension) val generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(s3Secret.bucket, fileName, fileExtension.uploadExtension) val generatePresignedUrl = amazonS3.generatePresignedUrl(generatePresignedUrlRequest) - return ImageUrlDto(generatePresignedUrl.toString(), uuid) + return ImageUrlDto(generatePresignedUrl.toString(), imageKey) + } + + fun deleteForPromise(promiseId: Long, imageKey: String, fileExtension: ImageFileExtension) { + var fileName = getForPromiseFimeName(imageKey, promiseId, fileExtension) + amazonS3.deleteObject(DeleteObjectRequest(s3Secret.bucket, fileName)) + } + + fun deleteForUser(userId: Long, imageKey: String, fileExtension: ImageFileExtension) { + var fileName = getForUserFimeName(imageKey, userId, fileExtension) + amazonS3.deleteObject(DeleteObjectRequest(s3Secret.bucket, fileName)) } private fun getForPromiseFimeName(uuid: String, promiseId: Long, fileExtension: ImageFileExtension): String { diff --git a/Whatnow-Infrastructure/src/test/kotlin/com/depromeet/whatnow/config/s3/S3UploadPresignedUrlServiceTest.kt b/Whatnow-Infrastructure/src/test/kotlin/com/depromeet/whatnow/config/s3/S3ServiceTest.kt similarity index 86% rename from Whatnow-Infrastructure/src/test/kotlin/com/depromeet/whatnow/config/s3/S3UploadPresignedUrlServiceTest.kt rename to Whatnow-Infrastructure/src/test/kotlin/com/depromeet/whatnow/config/s3/S3ServiceTest.kt index 0f78ce6f..bb3c8729 100644 --- a/Whatnow-Infrastructure/src/test/kotlin/com/depromeet/whatnow/config/s3/S3UploadPresignedUrlServiceTest.kt +++ b/Whatnow-Infrastructure/src/test/kotlin/com/depromeet/whatnow/config/s3/S3ServiceTest.kt @@ -16,8 +16,8 @@ import org.springframework.core.env.Environment import kotlin.test.assertContains @ExtendWith(MockitoExtension::class) -class S3UploadPresignedUrlServiceTest { - lateinit var s3UploadPresignedUrlService: S3UploadPresignedUrlService +class S3ServiceTest { + lateinit var s3Service: S3Service var s3Properties: S3Properties = S3Properties( S3Properties.S3Secret( @@ -44,7 +44,7 @@ class S3UploadPresignedUrlServiceTest { @BeforeEach fun setup() { - s3UploadPresignedUrlService = S3UploadPresignedUrlService(amazonS3, s3Properties, springEnvironmentHelper) + s3Service = S3Service(amazonS3, s3Properties, springEnvironmentHelper) } @Test @@ -54,7 +54,7 @@ class S3UploadPresignedUrlServiceTest { val imageFIleExtension = ImageFileExtension.JPG // when - val presignedUrl = s3UploadPresignedUrlService.forPromise(promiseId, imageFIleExtension) + val presignedUrl = s3Service.getPresignedUrlForPromise(promiseId, imageFIleExtension) // then val resultUrl = "https://${s3Properties.s3.bucket}.${s3Properties.s3.endpoint.replace("https://", "")}/local/promise/$promiseId/${presignedUrl.key}.${imageFIleExtension.uploadExtension}" @@ -69,7 +69,7 @@ class S3UploadPresignedUrlServiceTest { val imageFIleExtension = ImageFileExtension.JPG // when - val presignedUrl = s3UploadPresignedUrlService.forUser(userId, imageFIleExtension) + val presignedUrl = s3Service.getPresignedUrlForUser(userId, imageFIleExtension) // then val resultUrl = "https://${s3Properties.s3.bucket}.${s3Properties.s3.endpoint.replace("https://", "")}/local/user/$userId/${presignedUrl.key}.${imageFIleExtension.uploadExtension}" From fb07499326d1c97980566b731a7c73079fde1ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=8F=84=EB=AA=A8?= Date: Sat, 8 Jul 2023 00:19:30 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[DPMBE-106]=20user=20profile=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=8A=94=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=ED=95=9C=EB=8B=A4=20(#161)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: user profile 수정 시 사용하지 않는 이미지는 삭제한다 * remove: imageFileExtension 사용하지 않아서 제거 * refactor: UserProfileImageUpdatedEvent 발행 Domain으로 이동하고 Kotlin takeIf 문법 사용 * style: spotless * style: 주석해제 & s3Service로 이름 변경 --- .../image/usecase/GetPresignedUrlUseCase.kt | 6 ++-- .../user/dto/request/UpdateProfileRequest.kt | 4 ++- .../api/user/usecase/UpdateUserUseCase.kt | 8 +++++- .../depromeet/whatnow/consts/WhatNowStatic.kt | 1 + .../domains/image/adapter/UserImageAdapter.kt | 4 +++ .../image/repository/UserImageRepository.kt | 1 + .../whatnow/domains/user/domain/User.kt | 14 ++++++---- .../domains/user/service/UserDomainService.kt | 4 +-- .../UserProfileImageUpdatedEvent.kt | 8 ++++++ .../UserProfileImageUpdatedEventHandler.kt | 28 +++++++++++++++++++ 10 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserProfileImageUpdatedEvent.kt create mode 100644 Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/UserProfileImageUpdatedEventHandler.kt diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt index fb8739a3..ff3fc1d2 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/image/usecase/GetPresignedUrlUseCase.kt @@ -8,14 +8,14 @@ import com.depromeet.whatnow.config.security.SecurityUtils @UseCase class GetPresignedUrlUseCase( - val presignedUrlService: S3Service, + val s3Service: S3Service, ) { fun forPromise(promiseId: Long, fileExtension: ImageFileExtension): ImageUrlResponse { - return ImageUrlResponse.from(presignedUrlService.getPresignedUrlForPromise(promiseId, fileExtension)) + return ImageUrlResponse.from(s3Service.getPresignedUrlForPromise(promiseId, fileExtension)) } fun forUser(fileExtension: ImageFileExtension): ImageUrlResponse { val currentUserId = SecurityUtils.currentUserId - return ImageUrlResponse.from(presignedUrlService.getPresignedUrlForUser(currentUserId, fileExtension)) + return ImageUrlResponse.from(s3Service.getPresignedUrlForUser(currentUserId, fileExtension)) } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/dto/request/UpdateProfileRequest.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/dto/request/UpdateProfileRequest.kt index 024f2d8f..10f6ef8a 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/dto/request/UpdateProfileRequest.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/dto/request/UpdateProfileRequest.kt @@ -1,6 +1,8 @@ package com.depromeet.whatnow.api.user.dto.request data class UpdateProfileRequest( - val profileImage: String, val username: String, + val profileImage: String, + val isDefaultImg: Boolean, + val imageKey: String, ) diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/usecase/UpdateUserUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/usecase/UpdateUserUseCase.kt index 2d614389..0bb62940 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/usecase/UpdateUserUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/user/usecase/UpdateUserUseCase.kt @@ -23,7 +23,13 @@ class UpdateUserUseCase( fun updateProfile(updateProfileRequest: UpdateProfileRequest): UserDetailVo { val currentUserId: Long = SecurityUtils.currentUserId - return userDomainService.updateProfile(currentUserId, updateProfileRequest.profileImage, updateProfileRequest.username) + return userDomainService.updateProfile( + currentUserId, + updateProfileRequest.profileImage, + updateProfileRequest.username, + updateProfileRequest.isDefaultImg, + updateProfileRequest.imageKey, + ) .toUserDetailVo() } } diff --git a/Whatnow-Common/src/main/kotlin/com/depromeet/whatnow/consts/WhatNowStatic.kt b/Whatnow-Common/src/main/kotlin/com/depromeet/whatnow/consts/WhatNowStatic.kt index 795ce05f..34b4d76c 100644 --- a/Whatnow-Common/src/main/kotlin/com/depromeet/whatnow/consts/WhatNowStatic.kt +++ b/Whatnow-Common/src/main/kotlin/com/depromeet/whatnow/consts/WhatNowStatic.kt @@ -29,6 +29,7 @@ const val RADIUS_WAIT_CONFIRM = 200 const val IMAGE_DOMAIN = "https://image.whatnow.kr" const val ASSERT_IMAGE_DOMAIN = "https://image.whatnow.kr/assert" +const val USER_DEFAULT_PROFILE_IMAGE = "https://image.whatnow.kr/assert/users/default.svg" const val REDIS_EXPIRE_EVENT_PATTERN = "__keyevent@*__:expired" diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt index fd5141ec..c72b14ed 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/adapter/UserImageAdapter.kt @@ -13,6 +13,10 @@ class UserImageAdapter( return userImageRepository.save(userImage) } + fun findAllByUserId(userId: Long): List { + return userImageRepository.findAllByUserId(userId) + } + fun findByUserIdAndImageKey(userId: Long, imageKey: String): UserImage { return userImageRepository.findByUserIdAndImageKey(userId, imageKey) ?: run { throw UserImageNotFoundException.EXCEPTION } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt index 9378b40f..05e69960 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/image/repository/UserImageRepository.kt @@ -6,6 +6,7 @@ import org.springframework.stereotype.Repository @Repository interface UserImageRepository : JpaRepository { + fun findAllByUserId(userId: Long): List fun findByUserIdAndImageKey(userId: Long, imageKey: String): UserImage? fun deleteByImageKeyAndUserId(imageKey: String, userId: Long) } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/domain/User.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/domain/User.kt index 6a1e76fd..2b3f4036 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/domain/User.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/domain/User.kt @@ -4,8 +4,10 @@ import com.depromeet.whatnow.common.BaseTimeEntity import com.depromeet.whatnow.common.aop.event.Events import com.depromeet.whatnow.common.vo.UserDetailVo import com.depromeet.whatnow.common.vo.UserInfoVo +import com.depromeet.whatnow.consts.USER_DEFAULT_PROFILE_IMAGE import com.depromeet.whatnow.domains.user.exception.AlreadyDeletedUserException import com.depromeet.whatnow.domains.user.exception.ForbiddenUserException +import com.depromeet.whatnow.events.domainEvent.UserProfileImageUpdatedEvent import com.depromeet.whatnow.events.domainEvent.UserSignUpEvent import java.time.LocalDateTime import javax.persistence.Column @@ -91,12 +93,14 @@ class User( fcmNotification = FcmNotificationVo.updateToken(fcmNotification, fcmToken) } - fun updateProfile(profileImage: String, username: String) { - if (profileImage != profileImg) { - isDefaultImg = false + fun updateProfile(profileImage: String, username: String, isDefaultImage: Boolean, imageKey: String) { + this.nickname = username + this.isDefaultImg = isDefaultImage + this.profileImg = profileImage.takeIf { !isDefaultImage } ?: USER_DEFAULT_PROFILE_IMAGE + + if (!isDefaultImage) { + Events.raise(UserProfileImageUpdatedEvent(this.id!!, imageKey)) } - profileImg = profileImage - nickname = username } fun toUserInfoVo(): UserInfoVo { diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/service/UserDomainService.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/service/UserDomainService.kt index ed2f9327..2c2b8262 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/service/UserDomainService.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/user/service/UserDomainService.kt @@ -103,9 +103,9 @@ class UserDomainService( } @Transactional - fun updateProfile(currentUserId: Long, profileImage: String, username: String): User { + fun updateProfile(currentUserId: Long, profileImage: String, username: String, isDefaultImage: Boolean, imageKey: String): User { val user = userAdapter.queryUser(currentUserId) - user.updateProfile(profileImage, username) + user.updateProfile(profileImage, username, isDefaultImage, imageKey) return user } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserProfileImageUpdatedEvent.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserProfileImageUpdatedEvent.kt new file mode 100644 index 00000000..f569755e --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/domainEvent/UserProfileImageUpdatedEvent.kt @@ -0,0 +1,8 @@ +package com.depromeet.whatnow.events.domainEvent + +import com.depromeet.whatnow.common.aop.event.DomainEvent + +class UserProfileImageUpdatedEvent( + val userId: Long, + val imageKey: String, +) : DomainEvent() diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/UserProfileImageUpdatedEventHandler.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/UserProfileImageUpdatedEventHandler.kt new file mode 100644 index 00000000..9b4903fc --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/events/handler/UserProfileImageUpdatedEventHandler.kt @@ -0,0 +1,28 @@ +package com.depromeet.whatnow.events.handler + +import com.depromeet.whatnow.annotation.Handler +import com.depromeet.whatnow.config.s3.S3Service +import com.depromeet.whatnow.domains.image.adapter.UserImageAdapter +import com.depromeet.whatnow.events.domainEvent.UserProfileImageUpdatedEvent +import org.springframework.scheduling.annotation.Async +import org.springframework.transaction.event.TransactionPhase +import org.springframework.transaction.event.TransactionalEventListener + +@Handler +class UserProfileImageUpdatedEventHandler( + val s3Service: S3Service, + val userImageAdapter: UserImageAdapter, +) { + @Async + @TransactionalEventListener(classes = [UserProfileImageUpdatedEvent::class], phase = TransactionPhase.AFTER_COMMIT) + fun handleRegisterUserEvent(userProfileImageUpdatedEvent: UserProfileImageUpdatedEvent) { + val userId = userProfileImageUpdatedEvent.userId + val imageKey = userProfileImageUpdatedEvent.imageKey + + userImageAdapter.findAllByUserId(userId) + .filter { it.imageKey != imageKey } + .map { + s3Service.deleteForUser(userId, it.imageKey, it.fileExtension) + } + } +} From 07e62b677c3631b710857796472539165828b3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=8F=84=EB=AA=A8?= Date: Sat, 8 Jul 2023 00:37:38 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[DPMBE-102]=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EC=95=BD=EC=86=8D=20=EB=AA=A8=EC=9D=8C=EC=A7=91=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=9D=91=EB=8B=B5=20=EA=B0=92=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../whatnow/api/promise/usecase/PromiseReadUseCase.kt | 8 +++++++- .../whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt index a5e64ce3..0c7659be 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt @@ -8,6 +8,7 @@ import com.depromeet.whatnow.api.promise.dto.PromiseFindDto import com.depromeet.whatnow.api.promise.dto.PromiseUserInfoVo import com.depromeet.whatnow.common.vo.UserInfoVo import com.depromeet.whatnow.config.security.SecurityUtils +import com.depromeet.whatnow.domains.image.adapter.PromiseImageAdapter import com.depromeet.whatnow.domains.interaction.adapter.InteractionAdapter import com.depromeet.whatnow.domains.promise.adaptor.PromiseAdaptor import com.depromeet.whatnow.domains.promise.domain.Promise @@ -27,6 +28,7 @@ class PromiseReadUseCase( val userAdapter: UserAdapter, val userRepository: UserRepository, val interactionAdapter: InteractionAdapter, + val promiseImageAdapter: PromiseImageAdapter, ) { /** * method desc: 유저가 참여한 약속들을 약속 종류(BEFORE, PAST)에 따라 분리해서 조회 @@ -116,6 +118,10 @@ class PromiseReadUseCase( } } + val promiseImagesUrls = promiseImageAdapter.findAllByPromiseId(promise.id!!) + .sortedByDescending { it.createdAt } + .map { it.uri } + val timeOverLocations = promiseUsers.mapNotNull { promiseUser -> promiseUser.userLocation?.let { location -> LocationCapture(userId = promiseUser.userId, coordinateVo = location) @@ -127,7 +133,7 @@ class PromiseReadUseCase( promise = promise, promiseUsers = promiseUserInfoVos, timeOverLocations = timeOverLocations, - promiseImageUrls = mutableListOf(), + promiseImageUrls = promiseImagesUrls, ), ) } diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt index acfc6c49..8a5cf457 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt @@ -2,6 +2,7 @@ package com.depromeet.whatnow.api.promise.usecase import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.common.vo.PlaceVo +import com.depromeet.whatnow.domains.image.adapter.PromiseImageAdapter import com.depromeet.whatnow.domains.interaction.adapter.InteractionAdapter import com.depromeet.whatnow.domains.interaction.domain.Interaction import com.depromeet.whatnow.domains.interaction.domain.InteractionType @@ -49,6 +50,9 @@ class PromiseReadUseCaseTest { @Mock private lateinit var interactionAdapter: InteractionAdapter + @Mock + private lateinit var promiseImageAdapter: PromiseImageAdapter + @InjectMocks private lateinit var promiseReadUseCase: PromiseReadUseCase @@ -61,6 +65,7 @@ class PromiseReadUseCaseTest { promiseAdaptor = promiseAdaptor, userAdapter = userAdapter, interactionAdapter = interactionAdapter, + promiseImageAdapter = promiseImageAdapter, ) val securityContext = SecurityContextHolder.createEmptyContext() val authentication = UsernamePasswordAuthenticationToken("1", null, setOf(SimpleGrantedAuthority("ROLE_USER")))