From 33d420ea5be69a5ff455eb237f6e7bc5f669245a Mon Sep 17 00:00:00 2001 From: kpeel Date: Tue, 6 May 2025 15:56:06 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=AA=BD=EC=A7=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20API=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../message/v2/DeleteMessageV2Controller.kt | 23 +++++++++ .../dto/response/MessageV2DetailsResponse.kt | 2 +- .../message/port/in/DeleteMessageV2UseCase.kt | 7 +++ .../service/v2/DeleteMessageV2Service.kt | 42 ++++++++++++++++ .../com/wespot/message/v2/MessageDetail.kt | 8 ++++ .../com/wespot/message/v2/MessageDetails.kt | 18 ++++++- .../com/wespot/message/v2/MessageRoom.kt | 48 +++++++++++++++++++ .../kotlin/com/wespot/message/v2/MessageV2.kt | 40 ++++++++++++++-- 8 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 app/src/main/kotlin/com/wespot/message/v2/DeleteMessageV2Controller.kt create mode 100644 core/src/main/kotlin/com/wespot/message/port/in/DeleteMessageV2UseCase.kt create mode 100644 core/src/main/kotlin/com/wespot/message/service/v2/DeleteMessageV2Service.kt diff --git a/app/src/main/kotlin/com/wespot/message/v2/DeleteMessageV2Controller.kt b/app/src/main/kotlin/com/wespot/message/v2/DeleteMessageV2Controller.kt new file mode 100644 index 00000000..de295dec --- /dev/null +++ b/app/src/main/kotlin/com/wespot/message/v2/DeleteMessageV2Controller.kt @@ -0,0 +1,23 @@ +package com.wespot.message.v2 + +import com.wespot.message.port.`in`.DeleteMessageV2UseCase +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/v2/messages") +class DeleteMessageV2Controller( + private val deleteMessageV2UseCase: DeleteMessageV2UseCase +) { + + @DeleteMapping("/{messageId}") + fun deleteMessage(@PathVariable messageId: Long): ResponseEntity { + deleteMessageV2UseCase.deleteMessage(messageId = messageId) + + return ResponseEntity.noContent() + .build() + } +} diff --git a/core/src/main/kotlin/com/wespot/message/dto/response/MessageV2DetailsResponse.kt b/core/src/main/kotlin/com/wespot/message/dto/response/MessageV2DetailsResponse.kt index 835d83ce..c7ece788 100644 --- a/core/src/main/kotlin/com/wespot/message/dto/response/MessageV2DetailsResponse.kt +++ b/core/src/main/kotlin/com/wespot/message/dto/response/MessageV2DetailsResponse.kt @@ -45,7 +45,7 @@ data class MessageV2DetailsResponse( name = room.receiverName(), messageRoomId = room.id(), isBookmarked = room.isBookmarked(), - messageDetails = room.messages.asList() + messageDetails = room.messageDetailsAsList() .map { MessageDetailResponse.from(it) } ) } diff --git a/core/src/main/kotlin/com/wespot/message/port/in/DeleteMessageV2UseCase.kt b/core/src/main/kotlin/com/wespot/message/port/in/DeleteMessageV2UseCase.kt new file mode 100644 index 00000000..e3b146f9 --- /dev/null +++ b/core/src/main/kotlin/com/wespot/message/port/in/DeleteMessageV2UseCase.kt @@ -0,0 +1,7 @@ +package com.wespot.message.port.`in` + +interface DeleteMessageV2UseCase { + + fun deleteMessage(messageId: Long) + +} diff --git a/core/src/main/kotlin/com/wespot/message/service/v2/DeleteMessageV2Service.kt b/core/src/main/kotlin/com/wespot/message/service/v2/DeleteMessageV2Service.kt new file mode 100644 index 00000000..9006550c --- /dev/null +++ b/core/src/main/kotlin/com/wespot/message/service/v2/DeleteMessageV2Service.kt @@ -0,0 +1,42 @@ +package com.wespot.message.service.v2 + +import com.wespot.auth.service.SecurityUtils +import com.wespot.message.port.`in`.DeleteMessageV2UseCase +import com.wespot.message.port.out.MessageV2Port +import com.wespot.message.v2.MessageRoom +import com.wespot.message.v2.MessageV2 +import com.wespot.user.port.out.UserPort +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class DeleteMessageV2Service( + private val userPort: UserPort, + private val messageV2Port: MessageV2Port +) : DeleteMessageV2UseCase { + + @Transactional + override fun deleteMessage(messageId: Long) { + val loginUser = SecurityUtils.getLoginUser(userPort = userPort) + + val message = messageV2Port.findById(id = messageId) + val allMessagesOfRoom = getAllMessagesOfRoom(message) + + val room = MessageRoom.of( + viewer = loginUser, + allMessagesOfRoom = allMessagesOfRoom, + ) + val deleteMessage = room.deleteMessage(messageId = messageId) + messageV2Port.save(messageV2 = deleteMessage) + } + + private fun getAllMessagesOfRoom(message: MessageV2): List { + if (message.isRoom()) { + return listOf(message) + messageV2Port.findAllByMessageRoomId(message.id) + } + + val messageRoom = messageV2Port.findById(id = message.messageRoomId!!) + return listOf(messageRoom) + messageV2Port.findAllByMessageRoomId(messageRoom.id) + } + +} diff --git a/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt b/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt index 9f82a1ea..6c237f29 100644 --- a/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt +++ b/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt @@ -57,4 +57,12 @@ data class MessageDetail( return message.answerMessage(viewer = sender, content = content) } + fun delete(deleter: User): MessageV2 { + return message.delete(deleter = deleter) + } + + fun isNotDeleted(viewer: User): Boolean { + return message.isDeleted(viewer = viewer) + } + } diff --git a/domain/src/main/kotlin/com/wespot/message/v2/MessageDetails.kt b/domain/src/main/kotlin/com/wespot/message/v2/MessageDetails.kt index 4dafdd68..e2ec051f 100644 --- a/domain/src/main/kotlin/com/wespot/message/v2/MessageDetails.kt +++ b/domain/src/main/kotlin/com/wespot/message/v2/MessageDetails.kt @@ -37,8 +37,8 @@ data class MessageDetails( return messages.map { it.chatTime() } } - fun asList(): List { - return messages + fun asList(viewer: User): List { + return asExcludeDeleteMessageDetails(viewer = viewer) } fun isAbleToAnswer(): Boolean { @@ -51,4 +51,18 @@ data class MessageDetails( return toAnswerMessage.createAnswerMessage(sender = sender, content = content) } + fun deleteMessage(viewer: User, messageId: Long): MessageV2 { + val toDeleteMessage = messages.find { it.message.id == messageId } ?: throw CustomException( + message = "삭제하려는 쪽지를 찾을 수 없습니다.", + status = HttpStatus.BAD_REQUEST, + view = ExceptionView.TOAST, + ) + + return toDeleteMessage.delete(deleter = viewer) + } + + private fun asExcludeDeleteMessageDetails(viewer: User): List { + return messages.filter { it.isNotDeleted(viewer = viewer) } + } + } diff --git a/domain/src/main/kotlin/com/wespot/message/v2/MessageRoom.kt b/domain/src/main/kotlin/com/wespot/message/v2/MessageRoom.kt index d42ead0b..0e65fdeb 100644 --- a/domain/src/main/kotlin/com/wespot/message/v2/MessageRoom.kt +++ b/domain/src/main/kotlin/com/wespot/message/v2/MessageRoom.kt @@ -43,6 +43,33 @@ data class MessageRoom( ) } + fun of(viewer: User, allMessagesOfRoom: List): MessageRoom { + val roomMessage = allMessagesOfRoom.find { it.isRoom() } ?: throw CustomException( + message = "쪽지 방이 존재하지 않습니다.", + status = HttpStatus.BAD_REQUEST, + view = ExceptionView.TOAST, + ) + + if (!roomMessage.isAbleToView(viewer = viewer)) { + throw CustomException( + message = "해당 쪽지 방을 볼 수 있는 권한이 존재하지 않습니다.", + status = HttpStatus.FORBIDDEN, + view = ExceptionView.TOAST, + ) + } + + val messageDetails = allMessagesOfRoom.filter { !it.isRoom() } + + return MessageRoom( + viewer = viewer, + roomMessage = roomMessage, + messages = MessageDetails.of( + viewer = viewer, + messages = listOf(roomMessage) + messageDetails.filter { it.isContainsOf(roomMessage) } + ) + ) + } + } fun isExistsUnReadMessage(): Boolean { @@ -129,4 +156,25 @@ data class MessageRoom( return messages.answer(sender = sender, content = validatedContent) } + fun deleteMessage(messageId: Long): MessageV2 { + val excludedDeletedMessage = messageDetailsAsList() + + if (excludedDeletedMessage.size <= 1) { + throw CustomException( + message = "해당 쪽지를 삭제하면 쪽지 방에 남은 쪽지가 존재하지 않습니다.", + status = HttpStatus.BAD_REQUEST, + view = ExceptionView.TOAST, + ) + } + + return messages.deleteMessage( + viewer = viewer, + messageId = messageId + ) + } + + fun messageDetailsAsList(): List { + return messages.asList(viewer = viewer) + } + } diff --git a/domain/src/main/kotlin/com/wespot/message/v2/MessageV2.kt b/domain/src/main/kotlin/com/wespot/message/v2/MessageV2.kt index 6d0db5db..3bf0d5dd 100644 --- a/domain/src/main/kotlin/com/wespot/message/v2/MessageV2.kt +++ b/domain/src/main/kotlin/com/wespot/message/v2/MessageV2.kt @@ -8,6 +8,7 @@ import com.wespot.message.event.MessageAnswerEvent import com.wespot.user.User import com.wespot.user.event.UsedAnswerFeatureEvent import com.wespot.user.message.AnonymousProfile +import jakarta.persistence.Id import org.springframework.http.HttpStatus import java.time.LocalDateTime @@ -25,11 +26,11 @@ data class MessageV2( val createdAt: LocalDateTime, val updatedAt: LocalDateTime, - val isSenderDeleted: Boolean, - val senderDeletedAt: LocalDateTime?, + var isSenderDeleted: Boolean, + var senderDeletedAt: LocalDateTime?, - val isReceiverDeleted: Boolean, - val receiverDeletedAt: LocalDateTime?, + var isReceiverDeleted: Boolean, + var receiverDeletedAt: LocalDateTime?, val messageRoomId: Long?, val messageRoomOwnerId: Long, @@ -363,6 +364,37 @@ data class MessageV2( isReceiverRead = true } + fun delete(deleter: User): MessageV2 { + val alreadyDeleted = isDeleted(viewer = deleter) + + if (alreadyDeleted) { + throw CustomException( + message = "이미 삭제된 쪽지입니다.", + status = HttpStatus.BAD_REQUEST, + view = ExceptionView.TOAST, + ) + } + + if (deleter.isMeSender(senderId = sender.id)) { + isSenderDeleted = true + senderDeletedAt = senderDeletedAt ?: LocalDateTime.now() + return this + } + + isReceiverDeleted = true + receiverDeletedAt = receiverDeletedAt ?: LocalDateTime.now() + return this + } + + fun isDeleted(viewer: User): Boolean { + if (viewer.isMeSender(senderId = sender.id)) { + return isSenderDeleted + } + + return isReceiverDeleted + } + + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is MessageV2) return false From 9cd354a556d7b07164491c495a0046f4cd50d4f4 Mon Sep 17 00:00:00 2001 From: kpeel Date: Tue, 6 May 2025 15:59:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20isNotDeleted=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=EB=AC=B8=20=EC=9E=98=EB=AA=BB=EB=90=98=EC=96=B4=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt b/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt index 6c237f29..330a65eb 100644 --- a/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt +++ b/domain/src/main/kotlin/com/wespot/message/v2/MessageDetail.kt @@ -62,7 +62,7 @@ data class MessageDetail( } fun isNotDeleted(viewer: User): Boolean { - return message.isDeleted(viewer = viewer) + return !message.isDeleted(viewer = viewer) } }