Skip to content

Commit

Permalink
정보 삭제 시 contribution도 삭제하기 (#225)
Browse files Browse the repository at this point in the history
* 정보 삭제 시 contribution도 삭제하기

* 테스트 작성
  • Loading branch information
Zeniuus authored Nov 3, 2023
1 parent 7175d73 commit d487349
Show file tree
Hide file tree
Showing 31 changed files with 557 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ dependencies {
implementation(projects.boundedContext.user.application)
implementation(projects.boundedContext.place.application)
implementation(projects.boundedContext.challenge.application)

implementation(projects.domainEvent)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,25 @@ import club.staircrusher.accessibility.application.port.out.persistence.Building
import club.staircrusher.accessibility.application.port.out.persistence.BuildingAccessibilityRepository
import club.staircrusher.accessibility.application.port.out.persistence.PlaceAccessibilityCommentRepository
import club.staircrusher.accessibility.application.port.out.persistence.PlaceAccessibilityRepository
import club.staircrusher.accessibility.domain.model.BuildingAccessibility
import club.staircrusher.accessibility.domain.model.PlaceAccessibility
import club.staircrusher.domain_event.BuildingAccessibilityCommentDeletedEvent
import club.staircrusher.domain_event.BuildingAccessibilityDeletedEvent
import club.staircrusher.domain_event.PlaceAccessibilityCommentDeletedEvent
import club.staircrusher.domain_event.PlaceAccessibilityDeletedEvent
import club.staircrusher.domain_event.dto.AccessibilityCommentRegistererDTO
import club.staircrusher.domain_event.dto.AccessibilityRegistererDTO
import club.staircrusher.place.application.port.`in`.PlaceService
import club.staircrusher.place.application.port.`in`.toBuildingDTO
import club.staircrusher.place.application.port.`in`.toPlaceDTO
import club.staircrusher.place.domain.model.Building
import club.staircrusher.place.domain.model.Place
import club.staircrusher.stdlib.di.annotation.Component
import club.staircrusher.stdlib.domain.SccDomainException
import club.staircrusher.stdlib.domain.event.DomainEventPublisher
import club.staircrusher.stdlib.persistence.TransactionIsolationLevel
import club.staircrusher.stdlib.persistence.TransactionManager
import kotlinx.coroutines.runBlocking

@Component
class DeleteAccessibilityUseCase(
Expand All @@ -18,6 +32,7 @@ class DeleteAccessibilityUseCase(
private val buildingAccessibilityRepository: BuildingAccessibilityRepository,
private val buildingAccessibilityCommentRepository: BuildingAccessibilityCommentRepository,
private val placeService: PlaceService,
private val domainEventPublisher: DomainEventPublisher,
) {
fun handle(
userId: String,
Expand All @@ -27,14 +42,89 @@ class DeleteAccessibilityUseCase(
if (!placeAccessibility.isDeletable(userId)) {
throw SccDomainException("삭제 가능한 장소 정보가 아닙니다.")
}
placeAccessibilityRepository.remove(placeAccessibilityId)
placeAccessibilityCommentRepository.removeByPlaceId(placeAccessibility.placeId)

val buildingId = placeService.findPlace(placeAccessibility.placeId)!!.building.id
if (placeAccessibilityRepository.findByBuildingId(buildingId).isEmpty()) {
val buildingAccessibility = buildingAccessibilityRepository.findByBuildingId(buildingId) ?: return@doInTransaction
buildingAccessibilityRepository.remove(buildingAccessibility.id)
buildingAccessibilityCommentRepository.removeByBuildingId(buildingAccessibility.buildingId)

val place = placeService.findPlace(placeAccessibility.placeId)!!
deletePlaceAccessibility(userId, placeAccessibility, place)
deletePlaceAccessibilityComments(place)

val building = place.building
if (placeAccessibilityRepository.findByBuildingId(building.id).isEmpty()) {
val buildingAccessibility = buildingAccessibilityRepository.findByBuildingId(building.id) ?: return@doInTransaction
deleteBuildingAccessibility(buildingAccessibility, building)
deleteBuildingAccessibilityComments(building)
}
}

private fun deletePlaceAccessibility(
userId: String,
placeAccessibility: PlaceAccessibility,
place: Place,
) {
placeAccessibilityRepository.remove(placeAccessibility.id)
transactionManager.doAfterCommit {
runBlocking {
domainEventPublisher.publishEvent(PlaceAccessibilityDeletedEvent(
id = placeAccessibility.id,
accessibilityRegisterer = AccessibilityRegistererDTO(
id = userId,
),
place = place.toPlaceDTO(),
))
}
}
}

private fun deletePlaceAccessibilityComments(place: Place) {
val placeAccessibilityComments = placeAccessibilityCommentRepository.findByPlaceId(place.id)
placeAccessibilityCommentRepository.removeByPlaceId(place.id)
transactionManager.doAfterCommit {
runBlocking {
placeAccessibilityComments.forEach { comment ->
domainEventPublisher.publishEvent(PlaceAccessibilityCommentDeletedEvent(
id = comment.id,
commentRegisterer = AccessibilityCommentRegistererDTO(
id = comment.userId,
),
place = place.toPlaceDTO(),
))
}
}
}
}

private fun deleteBuildingAccessibility(
buildingAccessibility: BuildingAccessibility,
building: Building,
) {
buildingAccessibilityRepository.remove(buildingAccessibility.id)
transactionManager.doAfterCommit {
runBlocking {
domainEventPublisher.publishEvent(BuildingAccessibilityDeletedEvent(
id = buildingAccessibility.id,
accessibilityRegisterer = AccessibilityRegistererDTO(
id = buildingAccessibility.userId,
),
building = building.toBuildingDTO(),
))
}
}
}

private fun deleteBuildingAccessibilityComments(building: Building) {
val buildingAccessibilityComments = buildingAccessibilityCommentRepository.findByBuildingId(building.id)
buildingAccessibilityCommentRepository.removeByBuildingId(building.id)
transactionManager.doAfterCommit {
runBlocking {
buildingAccessibilityComments.forEach { comment ->
domainEventPublisher.publishEvent(BuildingAccessibilityCommentDeletedEvent(
id = comment.id,
commentRegisterer = AccessibilityCommentRegistererDTO(
id = comment.userId,
),
building = building.toBuildingDTO(),
))
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,7 @@ class RegisterBuildingAccessibilityUseCase(
userId = userId,
contribution = ChallengeService.Contribution.BuildingAccessibility(
buildingAccessibilityId = registerResult.buildingAccessibility.id,
buildingAccessibilityAddress = registerResult.building.address.let {
ChallengeAddress(
siDo = it.siDo,
siGunGu = it.siGunGu,
eupMyeonDong = it.eupMyeonDong,
li = it.li,
roadName = it.roadName
)
}
buildingAccessibilityAddress = ChallengeAddress(registerResult.building),
)
)
return@doInTransaction RegisterBuildingAccessibilityUseCaseResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import club.staircrusher.stdlib.persistence.TransactionManager
class RegisterPlaceAccessibilityUseCase(
private val transactionManager: TransactionManager,
private val accessibilityApplicationService: AccessibilityApplicationService,
private val challengeService: ChallengeService
private val challengeService: ChallengeService,
) {
data class RegisterPlaceAccessibilityUseCaseResult(
val registerPlaceAccessibilityResult: RegisterPlaceAccessibilityResult,
Expand All @@ -23,7 +23,7 @@ class RegisterPlaceAccessibilityUseCase(
)

fun handle(
userId: String?,
userId: String,
createPlaceAccessibilityParams: PlaceAccessibilityRepository.CreateParams,
createPlaceAccessibilityCommentParams: PlaceAccessibilityCommentRepository.CreateParams?,
): RegisterPlaceAccessibilityUseCaseResult = transactionManager.doInTransaction {
Expand All @@ -33,23 +33,14 @@ class RegisterPlaceAccessibilityUseCase(
)
val getAccessibilityResult =
accessibilityApplicationService.doGetAccessibility(createPlaceAccessibilityParams.placeId, userId)
val challengeContributions = userId?.let { userId ->
challengeService.contributeToSatisfiedChallenges(
userId = userId,
contribution = ChallengeService.Contribution.PlaceAccessibility(
placeAccessibilityId = registerResult.placeAccessibility.id,
placeAccessibilityAddress = registerResult.place.address.let {
ChallengeAddress(
siDo = it.siDo,
siGunGu = it.siGunGu,
eupMyeonDong = it.eupMyeonDong,
li = it.li,
roadName = it.roadName
)
}
)
val challengeContributions = challengeService.contributeToSatisfiedChallenges(
userId = userId,
contribution = ChallengeService.Contribution.PlaceAccessibility(
placeAccessibilityId = registerResult.placeAccessibility.id,
placeAccessibilityAddress = ChallengeAddress(registerResult.place),
)
} ?: listOf()
)

return@doInTransaction RegisterPlaceAccessibilityUseCaseResult(
registerPlaceAccessibilityResult = registerResult,
getAccessibilityResult = getAccessibilityResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ dependencies {
val awsSdkVersion: String by project
implementation("software.amazon.awssdk:s3:$awsSdkVersion")
runtimeOnly("software.amazon.awssdk:sts:$awsSdkVersion") // IRSA를 사용하기 위해서 필요함
testImplementation(projects.domainEvent)
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,23 @@ import club.staircrusher.accesssibility.infra.adapter.`in`.controller.base.Acces
import club.staircrusher.api.spec.dto.AccessibilityInfoDto
import club.staircrusher.api.spec.dto.DeleteAccessibilityPostRequest
import club.staircrusher.api.spec.dto.GetAccessibilityPostRequest
import club.staircrusher.domain_event.PlaceAccessibilityDeletedEvent
import club.staircrusher.domain_event.PlaceAccessibilityCommentDeletedEvent
import club.staircrusher.domain_event.BuildingAccessibilityDeletedEvent
import club.staircrusher.domain_event.BuildingAccessibilityCommentDeletedEvent
import club.staircrusher.stdlib.domain.event.DomainEventPublisher
import club.staircrusher.testing.spring_it.mock.MockSccClock
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.reset
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.mock.mockito.MockBean
import java.time.Duration

class DeleteAccessibilityTest : AccessibilityITBase() {
Expand All @@ -25,6 +36,9 @@ class DeleteAccessibilityTest : AccessibilityITBase() {
@Autowired
lateinit var buildingAccessibilityRepository: BuildingAccessibilityRepository

@MockBean
lateinit var domainEventPublisher: DomainEventPublisher

@Test
fun `전반적인 테스트`() {
// given: 한 건물에 두 개의 장소 정보를 등록한다.
Expand All @@ -43,6 +57,12 @@ class DeleteAccessibilityTest : AccessibilityITBase() {
}
}

runBlocking {
verify(domainEventPublisher, times(1)).publishEvent(any<PlaceAccessibilityDeletedEvent>())
verify(domainEventPublisher, times(1)).publishEvent(any<PlaceAccessibilityCommentDeletedEvent>())
verify(domainEventPublisher, times(0)).publishEvent(any<BuildingAccessibilityDeletedEvent>())
verify(domainEventPublisher, times(0)).publishEvent(any<BuildingAccessibilityCommentDeletedEvent>())
}
val getAccessibilityParams1 = GetAccessibilityPostRequest(placeId = place1.id)
mvc
.sccRequest("/getAccessibility", getAccessibilityParams1)
Expand All @@ -55,6 +75,7 @@ class DeleteAccessibilityTest : AccessibilityITBase() {
}

// when: 마지막 남은 장소 정보를 삭제한다
reset(domainEventPublisher) // 메소드 호출 횟수를 리셋해준다.
val deleteAccessibilityParams2 = DeleteAccessibilityPostRequest(placeAccessibilityId = placeAccessibility2.id)
mvc
.sccRequest("/deleteAccessibility", deleteAccessibilityParams2, user = user)
Expand All @@ -66,6 +87,13 @@ class DeleteAccessibilityTest : AccessibilityITBase() {
}
}

runBlocking {
verify(domainEventPublisher, times(1)).publishEvent(any<PlaceAccessibilityDeletedEvent>())
verify(domainEventPublisher, times(1)).publishEvent(any<PlaceAccessibilityCommentDeletedEvent>())
// 건물 정보는 이중으로 등록되지 않으므로 1개만 삭제되고, 건물 코멘트는 이중 등록이 되므로 2개가 삭제된다.
verify(domainEventPublisher, times(1)).publishEvent(any<BuildingAccessibilityDeletedEvent>())
verify(domainEventPublisher, times(2)).publishEvent(any<BuildingAccessibilityCommentDeletedEvent>())
}
val getAccessibilityParams2 = GetAccessibilityPostRequest(placeId = place1.id)
mvc
.sccRequest("/getAccessibility", getAccessibilityParams2)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dependencies {
implementation(projects.boundedContext.user.application)
implementation(projects.boundedContext.place.application)
implementation(projects.domainEvent)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,34 @@ class ChallengeService(
private val clock: Clock,
) {
sealed class Contribution(val address: ChallengeAddress) {
abstract val actionType: ChallengeActionCondition.Type
data class PlaceAccessibility(
val placeAccessibilityId: String,
val placeAccessibilityAddress: ChallengeAddress
) : Contribution(placeAccessibilityAddress)
val placeAccessibilityAddress: ChallengeAddress,
) : Contribution(placeAccessibilityAddress) {
override val actionType = ChallengeActionCondition.Type.PLACE_ACCESSIBILITY
}

data class PlaceAccessibilityComment(
val placeAccessibilityCommentId: String,
val placeAccessibilityAddress: ChallengeAddress
) : Contribution(placeAccessibilityAddress)
val placeAccessibilityAddress: ChallengeAddress,
) : Contribution(placeAccessibilityAddress) {
override val actionType = ChallengeActionCondition.Type.PLACE_ACCESSIBILITY_COMMENT
}

data class BuildingAccessibility(
val buildingAccessibilityId: String,
val buildingAccessibilityAddress: ChallengeAddress
) : Contribution(buildingAccessibilityAddress)
val buildingAccessibilityAddress: ChallengeAddress,
) : Contribution(buildingAccessibilityAddress) {
override val actionType = ChallengeActionCondition.Type.BUILDING_ACCESSIBILITY
}

data class BuildingAccessibilityComment(
val buildingAccessibilityCommentId: String,
val buildingAccessibilityAddress: ChallengeAddress
) : Contribution(buildingAccessibilityAddress)
) : Contribution(buildingAccessibilityAddress) {
override val actionType = ChallengeActionCondition.Type.BUILDING_ACCESSIBILITY_COMMENT
}
}

fun getMyInProgressChallenges(userId: String, criteriaTime: Instant = clock.instant()): List<Challenge> {
Expand Down Expand Up @@ -95,12 +104,7 @@ class ChallengeService(
ch.conditions.firstOrNull { cond ->
cond.isSatisfied(
address = contribution.address,
actionType = when (contribution) {
is Contribution.PlaceAccessibility -> ChallengeActionCondition.Type.PLACE_ACCESSIBILITY
is Contribution.PlaceAccessibilityComment -> ChallengeActionCondition.Type.PLACE_ACCESSIBILITY_COMMENT
is Contribution.BuildingAccessibility -> ChallengeActionCondition.Type.BUILDING_ACCESSIBILITY
is Contribution.BuildingAccessibilityComment -> ChallengeActionCondition.Type.BUILDING_ACCESSIBILITY_COMMENT
}
actionType = contribution.actionType,
)
} != null
}
Expand Down Expand Up @@ -182,4 +186,18 @@ class ChallengeService(
}
}
}

fun deleteContributions(
userId: String,
contribution: Contribution,
) {
val myInProgressChallenges = getMyInProgressChallenges(userId = userId)
myInProgressChallenges
.mapNotNull { myInProgressChallenge ->
getExistingContribution(myInProgressChallenge.id, contribution)
}
.forEach {
challengeContributionRepository.remove(it.id)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package club.staircrusher.challenge.application.port.`in`.use_case

import club.staircrusher.challenge.application.port.`in`.ChallengeService
import club.staircrusher.challenge.domain.model.ChallengeAddress
import club.staircrusher.domain_event.BuildingAccessibilityCommentDeletedEvent
import club.staircrusher.place.application.port.`in`.toBuilding
import club.staircrusher.stdlib.di.annotation.Component
import club.staircrusher.stdlib.persistence.TransactionManager

@Component
class HandleBuildingAccessibilityCommentDeletedEventUseCase(
private val transactionManager: TransactionManager,
private val challengeService: ChallengeService,
) {
fun handle(event: BuildingAccessibilityCommentDeletedEvent) = transactionManager.doInTransaction {
if (event.commentRegisterer.id == null) {
return@doInTransaction
}
challengeService.deleteContributions(
userId = event.commentRegisterer.id!!,
contribution = ChallengeService.Contribution.BuildingAccessibility(
buildingAccessibilityId = event.id,
buildingAccessibilityAddress = ChallengeAddress(event.building.toBuilding()),
),
)
}
}
Loading

0 comments on commit d487349

Please sign in to comment.