From 0ff5ea26fbe2de5f9c957e25bb0f9b147c3acde4 Mon Sep 17 00:00:00 2001 From: Sim-km Date: Sun, 13 Oct 2024 22:37:20 +0900 Subject: [PATCH] =?UTF-8?q?ASAP-189=20feat:=20=EC=A7=80=EA=B8=88=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=20=EC=A0=84=EC=86=A1=ED=95=9C=20=ED=8E=B8=EC=A7=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20Api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../letter/port/in/GetSendLetterUsecase.kt | 23 ++++++++ .../port/out/SendLetterManagementPort.kt | 2 + .../letter/service/LetterQueryService.kt | 12 +++- .../letter/service/LetterQueryServiceTest.kt | 21 +++++-- .../application/letter/LetterMockManager.kt | 7 +-- .../asap/bootstrap/letter/api/LetterApi.kt | 6 ++ .../letter/controller/LetterController.kt | 21 +++++++ .../letter/dto/SendLetterHistoryResponse.kt | 9 +++ .../letter/LetterAcceptanceSupporter.kt | 3 + .../letter/controller/LetterControllerTest.kt | 56 ++++++++++++++++++- .../letter/LetterApiIntegrationTest.kt | 52 ++++++++++++++++- .../com/asap/common/page/ListResponse.kt | 10 ++++ .../com/asap/common/page/PageResponse.kt | 16 +++--- .../kotlin/com/asap/domain/LetterFixture.kt | 29 ++++++++++ .../adapter/SendLetterManagementJpaAdapter.kt | 5 ++ .../repository/SendLetterJpaRepository.kt | 16 ++++++ 16 files changed, 266 insertions(+), 22 deletions(-) create mode 100644 Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GetSendLetterUsecase.kt create mode 100644 Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/SendLetterHistoryResponse.kt create mode 100644 Common-Module/src/main/kotlin/com/asap/common/page/ListResponse.kt create mode 100644 Domain-Module/src/testFixtures/kotlin/com/asap/domain/LetterFixture.kt diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GetSendLetterUsecase.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GetSendLetterUsecase.kt new file mode 100644 index 0000000..af5dd8b --- /dev/null +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GetSendLetterUsecase.kt @@ -0,0 +1,23 @@ +package com.asap.application.letter.port.`in` + +import java.time.LocalDate + +interface GetSendLetterUsecase { + fun getHistory(query: Query.AllHistory): List + + sealed class Query { + + data class AllHistory( + val userId: String, + ) : Query() + } + + sealed class Response { + + data class History( + val letterId: String, + val receiverName: String, + val sendDate: LocalDate, + ) : Response() + } +} diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt index d8f54ce..beab436 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/SendLetterManagementPort.kt @@ -34,4 +34,6 @@ interface SendLetterManagementPort { receiverId: DomainId, letterCode: String, ): Boolean + + fun getAllSendLetter(senderId: DomainId): List } diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterQueryService.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterQueryService.kt index bce1fbb..af0b38c 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterQueryService.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/service/LetterQueryService.kt @@ -23,7 +23,8 @@ class LetterQueryService( GetIndependentLettersUsecase, GetSpaceLettersUsecase, GetSpaceLetterDetailUsecase, - GetAllLetterCountUsecase { + GetAllLetterCountUsecase, + GetSendLetterUsecase { override fun get(query: GetVerifiedLetterUsecase.Query): GetVerifiedLetterUsecase.Response { sendLetterManagementPort .getReadLetterNotNull( @@ -171,4 +172,13 @@ class LetterQueryService( count = independentLetterCount + spaceLetterCount, ) } + + override fun getHistory(query: GetSendLetterUsecase.Query.AllHistory): List = + sendLetterManagementPort.getAllSendLetter(DomainId(query.userId)).map { + GetSendLetterUsecase.Response.History( + letterId = it.id.value, + receiverName = it.receiverName, + sendDate = it.createdDate, + ) + } } diff --git a/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt b/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt index a4d3499..bf40b44 100644 --- a/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt +++ b/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt @@ -1,14 +1,12 @@ package com.asap.application.letter.service -import com.asap.application.letter.port.`in`.GetAllLetterCountUsecase -import com.asap.application.letter.port.`in`.GetIndependentLettersUsecase -import com.asap.application.letter.port.`in`.GetSpaceLetterDetailUsecase -import com.asap.application.letter.port.`in`.GetVerifiedLetterUsecase +import com.asap.application.letter.port.`in`.* import com.asap.application.letter.port.out.IndependentLetterManagementPort import com.asap.application.letter.port.out.SendLetterManagementPort import com.asap.application.letter.port.out.SpaceLetterManagementPort import com.asap.application.space.port.out.SpaceManagementPort import com.asap.application.user.port.out.UserManagementPort +import com.asap.domain.LetterFixture import com.asap.domain.UserFixture import com.asap.domain.common.DomainId import com.asap.domain.letter.entity.IndependentLetter @@ -365,4 +363,19 @@ class LetterQueryServiceTest : } } } + + given("작성한 편지에 대해 조회할 떄") { + val sender = UserFixture.createUser() + val query = GetSendLetterUsecase.Query.AllHistory(sender.id.value) + val sendLetters = LetterFixture.generateSendLetter(senderId = sender.id) + every { mockSendLetterManagementPort.getAllSendLetter(sender.id) } returns listOf(sendLetters) + `when`("전체 편지 조회 요청이 들어오면") { + val response = letterQueryService.getHistory(query) + then("전체 편지를 반환한다") { + response[0].letterId shouldBe sendLetters.id.value + response[0].receiverName shouldBe sendLetters.receiverName + response[0].sendDate shouldBe sendLetters.createdDate + } + } + } }) diff --git a/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt b/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt index 96648ae..fde993e 100644 --- a/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt +++ b/Application-Module/src/testFixtures/kotlin/com/asap/application/letter/LetterMockManager.kt @@ -27,7 +27,7 @@ class LetterMockManager( fun generateMockSendLetter( receiverName: String, senderId: String = DomainId.generate().value, - ): Map { + ): SendLetter { val sendLetter = SendLetter( receiverName = receiverName, @@ -45,10 +45,7 @@ class LetterMockManager( ), ) sendLetterManagementPort.save(sendLetter) - return mapOf( - "letterCode" to sendLetter.letterCode!!, - "letterId" to sendLetter.id.value, - ) + return sendLetter } fun generateMockReadLetter( diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt index a09caf5..2b00726 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/api/LetterApi.kt @@ -3,6 +3,7 @@ package com.asap.bootstrap.letter.api import com.asap.bootstrap.common.exception.ExceptionResponse import com.asap.bootstrap.common.security.annotation.AccessUser import com.asap.bootstrap.letter.dto.* +import com.asap.common.page.ListResponse import com.asap.common.page.SliceResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content @@ -254,4 +255,9 @@ interface LetterApi { fun getLetterCount( @AccessUser userId: String, ): AllLetterCountResponse + + @GetMapping("/send") + fun getSendLetterHistory( + @AccessUser userId: String, + ): ListResponse } diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt index 8308e8b..7703726 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/controller/LetterController.kt @@ -3,6 +3,7 @@ package com.asap.bootstrap.letter.controller import com.asap.application.letter.port.`in`.* import com.asap.bootstrap.letter.api.LetterApi import com.asap.bootstrap.letter.dto.* +import com.asap.common.page.ListResponse import com.asap.common.page.SliceResponse import org.springframework.web.bind.annotation.RestController @@ -16,6 +17,7 @@ class LetterController( private val removeLetterUsecase: RemoveLetterUsecase, private val updateLetterUsecase: UpdateLetterUsecase, private val getAllLetterCountUsecase: GetAllLetterCountUsecase, + private val getSendLetterUsecase: GetSendLetterUsecase, ) : LetterApi { override fun verifyLetter( request: LetterVerifyRequest, @@ -196,4 +198,23 @@ class LetterController( count = response.count, ) } + + override fun getSendLetterHistory(userId: String): ListResponse { + val response = + getSendLetterUsecase.getHistory( + GetSendLetterUsecase.Query.AllHistory( + userId = userId, + ), + ) + return ListResponse.of( + content = + response.map { + SendLetterHistoryResponse( + letterId = it.letterId, + receiverName = it.receiverName, + sendDate = it.sendDate, + ) + }, + ) + } } diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/SendLetterHistoryResponse.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/SendLetterHistoryResponse.kt new file mode 100644 index 0000000..85f8bb7 --- /dev/null +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/letter/dto/SendLetterHistoryResponse.kt @@ -0,0 +1,9 @@ +package com.asap.bootstrap.letter.dto + +import java.time.LocalDate + +data class SendLetterHistoryResponse( + val letterId: String, + val receiverName: String, + val sendDate: LocalDate, +) diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/LetterAcceptanceSupporter.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/LetterAcceptanceSupporter.kt index 24fd544..ed548df 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/LetterAcceptanceSupporter.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/LetterAcceptanceSupporter.kt @@ -49,4 +49,7 @@ abstract class LetterAcceptanceSupporter : AcceptanceSupporter() { @MockBean lateinit var getAllLetterCountUsecase: GetAllLetterCountUsecase + + @MockBean + lateinit var getSendLetterUsecase: GetSendLetterUsecase } diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt index 097426a..05180e7 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/LetterControllerTest.kt @@ -378,7 +378,7 @@ class LetterControllerTest : LetterAcceptanceSupporter() { } @Test - fun getAllLetterCount() { + fun getAllLetterCount() { // given val accessToken = jwtMockManager.generateAccessToken() val response = GetAllLetterCountUsecase.Response(5) @@ -406,4 +406,58 @@ class LetterControllerTest : LetterAcceptanceSupporter() { } } } + + @Test + fun getAllSendLetterHistory() { + // given + val accessToken = jwtMockManager.generateAccessToken() + val response = + (0..2).map { + GetSendLetterUsecase.Response.History( + letterId = "letterId$it", + receiverName = "receiverName$it", + sendDate = LocalDate.now(), + ) + } + BDDMockito + .given( + getSendLetterUsecase.getHistory( + GetSendLetterUsecase.Query.AllHistory( + userId = "userId", + ), + ), + ).willReturn(response) + + // when + val result = + mockMvc.get("/api/v1/letters/send") { + contentType = MediaType.APPLICATION_JSON + header("Authorization", "Bearer $accessToken") + } + // then + result.andExpect { + status { isOk() } + jsonPath("$.content") { + exists() + isArray() + (0..2).forEach { + jsonPath("$.content[$it].letterId") { + exists() + isString() + isNotEmpty() + } + jsonPath("$.content[$it].receiverName") { + exists() + isString() + isNotEmpty() + } + jsonPath("$.content[$it].sendDate") { + exists() + isString() + isNotEmpty() + } + } + } + } + } } diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt index 8392ff0..5e22266 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/LetterApiIntegrationTest.kt @@ -32,7 +32,7 @@ class LetterApiIntegrationTest : IntegrationSupporter() { val userId = userMockManager.settingUser(username = "username") val accessToken = jwtMockManager.generateAccessToken(userId) val letterCode = - letterMockManager.generateMockSendLetter("username", senderId = senderId)["letterCode"] as String + letterMockManager.generateMockSendLetter("username", senderId = senderId).letterCode!! val request = LetterVerifyRequest(letterCode) // when val response = @@ -83,7 +83,7 @@ class LetterApiIntegrationTest : IntegrationSupporter() { val userId = userMockManager.settingUser(username = "username") val accessToken = jwtMockManager.generateAccessToken(userId) val letterCode = - letterMockManager.generateMockSendLetter("otherUsername_invalidUser", senderId)["letterCode"] as String + letterMockManager.generateMockSendLetter("otherUsername_invalidUser", senderId).letterCode!! val request = LetterVerifyRequest(letterCode) // when val response = @@ -658,4 +658,52 @@ class LetterApiIntegrationTest : IntegrationSupporter() { } } } + + @Test + fun getAllSendLetterHistory() { + // given + val senderId = userMockManager.settingUser() + val accessToken = jwtMockManager.generateAccessToken(senderId) + val sendLetters = + (0..3).map { + letterMockManager.generateMockSendLetter( + receiverName = "receiverName", + senderId = senderId, + ) + } + // when + val response = + mockMvc.get("/api/v1/letters/send") { + contentType = MediaType.APPLICATION_JSON + header("Authorization", "Bearer $accessToken") + } + // then + response.andExpect { + status { isOk() } + jsonPath("$.content") { + exists() + isArray() + (0..3).forEach { + jsonPath("$.content[$it].letterId") { + exists() + isString() + isNotEmpty() + value(sendLetters[it].id.value) + } + jsonPath("$.content[$it].receiverName") { + exists() + isString() + isNotEmpty() + value(sendLetters[it].receiverName) + } + jsonPath("$.content[$it].sendDate") { + exists() + isString() + isNotEmpty() + value(sendLetters[it].createdDate.toString()) + } + } + } + } + } } diff --git a/Common-Module/src/main/kotlin/com/asap/common/page/ListResponse.kt b/Common-Module/src/main/kotlin/com/asap/common/page/ListResponse.kt new file mode 100644 index 0000000..4d454ac --- /dev/null +++ b/Common-Module/src/main/kotlin/com/asap/common/page/ListResponse.kt @@ -0,0 +1,10 @@ +package com.asap.common.page + +data class ListResponse internal constructor( + val content: List, + val size: Int, +) { + companion object { + fun of(content: List): ListResponse = ListResponse(content, content.size) + } +} diff --git a/Common-Module/src/main/kotlin/com/asap/common/page/PageResponse.kt b/Common-Module/src/main/kotlin/com/asap/common/page/PageResponse.kt index 881f843..50ad18e 100644 --- a/Common-Module/src/main/kotlin/com/asap/common/page/PageResponse.kt +++ b/Common-Module/src/main/kotlin/com/asap/common/page/PageResponse.kt @@ -1,28 +1,26 @@ package com.asap.common.page -data class PageResponse( +data class PageResponse internal constructor( val content: List, val totalElements: Long, val totalPages: Int, val size: Int, - val page: Int + val page: Int, ) { - companion object { fun of( content: List, totalElements: Long, totalPages: Int, size: Int, - page: Int - ): PageResponse { - return PageResponse( + page: Int, + ): PageResponse = + PageResponse( content = content, totalElements = totalElements, totalPages = totalPages, size = size, - page = page + page = page, ) - } } -} \ No newline at end of file +} diff --git a/Domain-Module/src/testFixtures/kotlin/com/asap/domain/LetterFixture.kt b/Domain-Module/src/testFixtures/kotlin/com/asap/domain/LetterFixture.kt new file mode 100644 index 0000000..d4e1dc6 --- /dev/null +++ b/Domain-Module/src/testFixtures/kotlin/com/asap/domain/LetterFixture.kt @@ -0,0 +1,29 @@ +package com.asap.domain + +import com.asap.domain.common.DomainId +import com.asap.domain.letter.entity.SendLetter +import com.asap.domain.letter.enums.LetterStatus +import com.asap.domain.letter.vo.LetterContent + +object LetterFixture { + fun generateSendLetter( + senderId: DomainId = DomainId.generate(), + receiverName: String = "receiverName", + letterCode: String = "letterCode", + status: LetterStatus = LetterStatus.READ, + receiverId: DomainId = DomainId.generate(), + ): SendLetter = + SendLetter( + receiverName = receiverName, + content = + LetterContent( + content = "content", + templateType = 1, + images = mutableListOf("image1", "image2"), + ), + senderId = senderId, + letterCode = letterCode, + status = status, + receiverId = receiverId, + ) +} diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/adapter/SendLetterManagementJpaAdapter.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/adapter/SendLetterManagementJpaAdapter.kt index 60d7962..2075c70 100644 --- a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/adapter/SendLetterManagementJpaAdapter.kt +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/adapter/SendLetterManagementJpaAdapter.kt @@ -61,4 +61,9 @@ class SendLetterManagementJpaAdapter( letterCode = letterCode, receiverId = receiverId.value, ) + + override fun getAllSendLetter(senderId: DomainId): List = + sendLetterJpaRepository + .findAllActiveSendLetterBySenderId(senderId.value) + .map { SendLetterMapper.toSendLetter(it) } } diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/repository/SendLetterJpaRepository.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/repository/SendLetterJpaRepository.kt index d77d908..1e88f17 100644 --- a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/repository/SendLetterJpaRepository.kt +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/repository/SendLetterJpaRepository.kt @@ -67,6 +67,19 @@ interface SendLetterJpaRepository : JpaRepository { entityStatus: EntityStatus, ): SendLetterEntity? + @Query( + """ + SELECT s + FROM SendLetterEntity s + WHERE s.senderId = :senderId + AND s.entityStatus = :entityStatus + """, + ) + fun findAllBy( + senderId: String, + entityStatus: EntityStatus, + ): List + fun existsByLetterCodeAndReceiverId( letterCode: String, receiverId: String, @@ -89,3 +102,6 @@ fun SendLetterJpaRepository.findActiveSendLetterByIdAndReceiverIdAndLetterStatus receiverId: String, letterStatus: LetterStatus, ): SendLetterEntity? = findByIdAndReceiverIdAndLetterStatus(id, receiverId, letterStatus, EntityStatus.ACTIVE) + +fun SendLetterJpaRepository.findAllActiveSendLetterBySenderId(senderId: String): List = + findAllBy(senderId, EntityStatus.ACTIVE)