From 64df2917cc6bc1c26e9fd73c511dfd391bf94846 Mon Sep 17 00:00:00 2001 From: Jisu Lim <69844138+Ji-soo708@users.noreply.github.com> Date: Thu, 23 Jan 2025 19:18:19 +0900 Subject: [PATCH] =?UTF-8?q?[YS-178]=20feat:=20=EC=8B=A4=ED=97=98=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=9E=91=EC=84=B1=ED=95=9C=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=82=AD=EC=A0=9C=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add DefaultResponse * feat: add delete repository method * feat: add DeleteExperimentPost API * test: add DeleteExperimentPostUseCase test code * refact: refactor delete method in repository impl * refact: add `@Transactional` to delete method --- .../service/ExperimentPostService.kt | 8 ++- .../experiment/DeleteExperimentPostUseCase.kt | 23 ++++++++ .../experiment/ExperimentPostGateway.kt | 1 + .../experiment/ExperimentPostGatewayImpl.kt | 4 ++ .../controller/ExperimentPostController.kt | 15 ++++++ .../dto/response/member/DefaultResponse.kt | 14 +++++ .../api/mapper/ExperimentPostMapper.kt | 9 +++- .../DeleteExperimentPostUseCaseTest.kt | 52 +++++++++++++++++++ 8 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCase.kt create mode 100644 src/main/kotlin/com/dobby/backend/presentation/api/dto/response/member/DefaultResponse.kt create mode 100644 src/test/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCaseTest.kt diff --git a/src/main/kotlin/com/dobby/backend/application/service/ExperimentPostService.kt b/src/main/kotlin/com/dobby/backend/application/service/ExperimentPostService.kt index 9fc39aa2..491fd7b7 100644 --- a/src/main/kotlin/com/dobby/backend/application/service/ExperimentPostService.kt +++ b/src/main/kotlin/com/dobby/backend/application/service/ExperimentPostService.kt @@ -22,7 +22,8 @@ class ExperimentPostService( private val getExperimentPostApplyMethodUseCase: GetExperimentPostApplyMethodUseCase, private val generateExperimentPostPreSignedUrlUseCase: GenerateExperimentPostPreSignedUrlUseCase, private val updateExpiredExperimentPostUseCase: UpdateExpiredExperimentPostUseCase, - private val getExperimentPostTotalCountByCustomFilterUseCase: GetExperimentPostTotalCountByCustomFilterUseCase + private val getExperimentPostTotalCountByCustomFilterUseCase: GetExperimentPostTotalCountByCustomFilterUseCase, + private val deleteExperimentPostUseCase: DeleteExperimentPostUseCase ) { @Transactional fun createNewExperimentPost(input: CreateExperimentPostUseCase.Input): CreateExperimentPostUseCase.Output { @@ -89,4 +90,9 @@ class ExperimentPostService( fun getExperimentPostTotalCount(input: GetExperimentPostTotalCountByCustomFilterUseCase.Input): Int { return getExperimentPostTotalCountByCustomFilterUseCase.execute(input) } + + @Transactional + fun deleteExperimentPost(input: DeleteExperimentPostUseCase.Input) { + deleteExperimentPostUseCase.execute(input) + } } diff --git a/src/main/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCase.kt b/src/main/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCase.kt new file mode 100644 index 00000000..4de6732b --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCase.kt @@ -0,0 +1,23 @@ +package com.dobby.backend.application.usecase.experiment + +import com.dobby.backend.application.usecase.UseCase +import com.dobby.backend.domain.exception.ExperimentPostNotFoundException +import com.dobby.backend.domain.gateway.experiment.ExperimentPostGateway + +class DeleteExperimentPostUseCase( + private val experimentPostGateway: ExperimentPostGateway +): UseCase { + data class Input( + val memberId: Long, + val postId: Long + ) + + override fun execute(input: Input) { + val post = experimentPostGateway.findExperimentPostByMemberIdAndPostId( + memberId = input.memberId, + postId = input.postId + ) ?: throw ExperimentPostNotFoundException() + + experimentPostGateway.delete(post) + } +} diff --git a/src/main/kotlin/com/dobby/backend/domain/gateway/experiment/ExperimentPostGateway.kt b/src/main/kotlin/com/dobby/backend/domain/gateway/experiment/ExperimentPostGateway.kt index 56cb346f..f0dc2aac 100644 --- a/src/main/kotlin/com/dobby/backend/domain/gateway/experiment/ExperimentPostGateway.kt +++ b/src/main/kotlin/com/dobby/backend/domain/gateway/experiment/ExperimentPostGateway.kt @@ -28,4 +28,5 @@ interface ExperimentPostGateway { fun countExperimentPostsByMemberId(memberId: Long): Int fun findExperimentPostByMemberIdAndPostId(memberId: Long, postId: Long): ExperimentPost? fun countExperimentPostsByCustomFilter(customFilter: CustomFilter): Int + fun delete(post: ExperimentPost) } diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/gateway/experiment/ExperimentPostGatewayImpl.kt b/src/main/kotlin/com/dobby/backend/infrastructure/gateway/experiment/ExperimentPostGatewayImpl.kt index 49f81a87..aa4f7992 100644 --- a/src/main/kotlin/com/dobby/backend/infrastructure/gateway/experiment/ExperimentPostGatewayImpl.kt +++ b/src/main/kotlin/com/dobby/backend/infrastructure/gateway/experiment/ExperimentPostGatewayImpl.kt @@ -106,4 +106,8 @@ class ExperimentPostGatewayImpl( override fun countExperimentPostsByCustomFilter(customFilter: CustomFilter): Int { return experimentPostCustomRepository.countExperimentPostsByCustomFilter(customFilter) } + + override fun delete(post: ExperimentPost) { + experimentPostRepository.delete(ExperimentPostEntity.fromDomain(post)) + } } diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/controller/ExperimentPostController.kt b/src/main/kotlin/com/dobby/backend/presentation/api/controller/ExperimentPostController.kt index d15e142c..8fd254f1 100644 --- a/src/main/kotlin/com/dobby/backend/presentation/api/controller/ExperimentPostController.kt +++ b/src/main/kotlin/com/dobby/backend/presentation/api/controller/ExperimentPostController.kt @@ -17,6 +17,7 @@ import com.dobby.backend.presentation.api.dto.response.experiment.CreateExperime import com.dobby.backend.presentation.api.dto.response.experiment.ExperimentPostApplyMethodResponse import com.dobby.backend.presentation.api.dto.response.experiment.ExperimentPostCountsResponse import com.dobby.backend.presentation.api.dto.response.experiment.ExperimentPostDetailResponse +import com.dobby.backend.presentation.api.dto.response.member.DefaultResponse import com.dobby.backend.presentation.api.mapper.ExperimentPostMapper import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag @@ -45,6 +46,20 @@ class ExperimentPostController ( return ExperimentPostMapper.toCreateExperimentPostResponse(output) } + @PreAuthorize("hasRole('RESEARCHER')") + @DeleteMapping("/{postId}") + @Operation( + summary = "공고 삭제 API- 연구자 공고 삭제", + description = "연구자가 자신이 등록한 실험 공고를 삭제합니다." + ) + fun deleteExperimentPost( + @PathVariable postId: Long + ): DefaultResponse { + val input = ExperimentPostMapper.toDeleteExperimentPostUseCaseInput(postId) + experimentPostService.deleteExperimentPost(input) + return DefaultResponse.ok() + } + @PreAuthorize("hasRole('RESEARCHER')") @PutMapping("/{postId}") @Operation( diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/member/DefaultResponse.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/member/DefaultResponse.kt new file mode 100644 index 00000000..f8b28df9 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/member/DefaultResponse.kt @@ -0,0 +1,14 @@ +package com.dobby.backend.presentation.api.dto.response.member + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(description = "기본 Operation 응답") +data class DefaultResponse( + @Schema(description = "성공 유무", example = "true") + val success: Boolean +) { + companion object { + fun ok(): DefaultResponse = DefaultResponse(true) + fun fail(): DefaultResponse = DefaultResponse(false) + } +} diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/mapper/ExperimentPostMapper.kt b/src/main/kotlin/com/dobby/backend/presentation/api/mapper/ExperimentPostMapper.kt index c97a461f..adc74a5c 100644 --- a/src/main/kotlin/com/dobby/backend/presentation/api/mapper/ExperimentPostMapper.kt +++ b/src/main/kotlin/com/dobby/backend/presentation/api/mapper/ExperimentPostMapper.kt @@ -272,7 +272,7 @@ object ExperimentPostMapper { } - fun toGetExperimentPostsResponse( + fun toGetExperimentPostsResponse( output: List, page: Int, totalCount: Int, @@ -332,4 +332,11 @@ object ExperimentPostMapper { recruitStatus = customFilter.recruitStatus ) } + + fun toDeleteExperimentPostUseCaseInput(postId: Long): DeleteExperimentPostUseCase.Input { + return DeleteExperimentPostUseCase.Input( + memberId = getCurrentMemberId(), + postId = postId + ) + } } diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCaseTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCaseTest.kt new file mode 100644 index 00000000..dc675bd6 --- /dev/null +++ b/src/test/kotlin/com/dobby/backend/application/usecase/experiment/DeleteExperimentPostUseCaseTest.kt @@ -0,0 +1,52 @@ +package com.dobby.backend.application.usecase.experiment + +import com.dobby.backend.domain.exception.ExperimentPostNotFoundException +import com.dobby.backend.domain.gateway.experiment.ExperimentPostGateway +import com.dobby.backend.domain.model.experiment.ExperimentPost +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify + +class DeleteExperimentPostUseCaseTest : BehaviorSpec({ + + val experimentPostGateway = mockk() + val deleteExperimentPostUseCase = DeleteExperimentPostUseCase(experimentPostGateway) + + given("게시글을 삭제할 때") { + val memberId = 1L + val postId = 2L + val input = DeleteExperimentPostUseCase.Input(memberId, postId) + + val existingPost = mockk() + every { experimentPostGateway.findExperimentPostByMemberIdAndPostId(memberId, postId) } returns existingPost + + `when`("게시글이 존재하면") { + every { experimentPostGateway.delete(existingPost) } returns Unit + + then("게시글을 삭제하고 아무 값도 반환하지 않아야 한다") { + deleteExperimentPostUseCase.execute(input) + verify(exactly = 1) { experimentPostGateway.findExperimentPostByMemberIdAndPostId(memberId, postId) } + verify(exactly = 1) { experimentPostGateway.delete(existingPost) } + } + } + } + + given("게시글을 삭제하려는데 게시글이 존재하지 않는 경우") { + val memberId = 1L + val postId = 999L + val input = DeleteExperimentPostUseCase.Input(memberId, postId) + + every { experimentPostGateway.findExperimentPostByMemberIdAndPostId(memberId, postId) } returns null + + `when`("게시글을 찾지 못하면") { + then("ExperimentPostNotFoundException 예외가 발생해야 한다") { + shouldThrow { + deleteExperimentPostUseCase.execute(input) + } + verify(exactly = 1) { experimentPostGateway.findExperimentPostByMemberIdAndPostId(memberId, postId) } + } + } + } +})