diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GenerateDraftKeyUsecase.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GenerateDraftKeyUsecase.kt index 4b65e0a..e38f535 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GenerateDraftKeyUsecase.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/in/GenerateDraftKeyUsecase.kt @@ -1,11 +1,19 @@ package com.asap.application.letter.port.`in` interface GenerateDraftKeyUsecase { - fun command(command: Command): Response + fun command(command: Command.Send): Response - data class Command( - val userId: String, - ) + fun command(command: Command.Physical): Response + + sealed class Command { + data class Send( + val userId: String, + ): Command() + + data class Physical( + val userId: String, + ): Command() + } data class Response( val draftId: String, diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/ReceiveDraftLetterManagementPort.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/ReceiveDraftLetterManagementPort.kt new file mode 100644 index 0000000..2a916a4 --- /dev/null +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/port/out/ReceiveDraftLetterManagementPort.kt @@ -0,0 +1,7 @@ +package com.asap.application.letter.port.out + +import com.asap.domain.letter.entity.ReceiveDraftLetter + +interface ReceiveDraftLetterManagementPort { + fun save(receiveDraftLetter: ReceiveDraftLetter): ReceiveDraftLetter +} \ No newline at end of file diff --git a/Application-Module/src/main/kotlin/com/asap/application/letter/service/DraftLetterCommandService.kt b/Application-Module/src/main/kotlin/com/asap/application/letter/service/DraftLetterCommandService.kt index 3d0f493..e5b68ba 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/letter/service/DraftLetterCommandService.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/letter/service/DraftLetterCommandService.kt @@ -4,8 +4,10 @@ import com.asap.application.letter.port.`in`.GenerateDraftKeyUsecase import com.asap.application.letter.port.`in`.RemoveDraftLetterUsecase import com.asap.application.letter.port.`in`.UpdateDraftLetterUsecase import com.asap.application.letter.port.out.DraftLetterManagementPort +import com.asap.application.letter.port.out.ReceiveDraftLetterManagementPort import com.asap.domain.common.DomainId import com.asap.domain.letter.entity.DraftLetter +import com.asap.domain.letter.entity.ReceiveDraftLetter import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -13,15 +15,22 @@ import org.springframework.transaction.annotation.Transactional @Transactional class DraftLetterCommandService( private val draftLetterManagementPort: DraftLetterManagementPort, + private val receiveDraftLetterManagementPort: ReceiveDraftLetterManagementPort, ) : GenerateDraftKeyUsecase, UpdateDraftLetterUsecase, RemoveDraftLetterUsecase { - override fun command(command: GenerateDraftKeyUsecase.Command): GenerateDraftKeyUsecase.Response { + override fun command(command: GenerateDraftKeyUsecase.Command.Send): GenerateDraftKeyUsecase.Response { val draftLetter = DraftLetter.default(DomainId(command.userId)) draftLetterManagementPort.save(draftLetter) return GenerateDraftKeyUsecase.Response(draftLetter.id.value) } + override fun command(command: GenerateDraftKeyUsecase.Command.Physical): GenerateDraftKeyUsecase.Response { + val receiveDraftLetter = ReceiveDraftLetter.default(DomainId(command.userId)) + receiveDraftLetterManagementPort.save(receiveDraftLetter) + return GenerateDraftKeyUsecase.Response(receiveDraftLetter.id.value) + } + override fun command(command: UpdateDraftLetterUsecase.Command) { val draftLetter = draftLetterManagementPort.getDraftLetterNotNull( diff --git a/Application-Module/src/test/kotlin/com/asap/application/letter/service/DraftLetterCommandServiceTest.kt b/Application-Module/src/test/kotlin/com/asap/application/letter/service/DraftLetterCommandServiceTest.kt index 31dfab7..7efdd57 100644 --- a/Application-Module/src/test/kotlin/com/asap/application/letter/service/DraftLetterCommandServiceTest.kt +++ b/Application-Module/src/test/kotlin/com/asap/application/letter/service/DraftLetterCommandServiceTest.kt @@ -4,8 +4,10 @@ import com.asap.application.letter.port.`in`.GenerateDraftKeyUsecase import com.asap.application.letter.port.`in`.RemoveDraftLetterUsecase import com.asap.application.letter.port.`in`.UpdateDraftLetterUsecase import com.asap.application.letter.port.out.DraftLetterManagementPort +import com.asap.application.letter.port.out.ReceiveDraftLetterManagementPort import com.asap.domain.common.DomainId import com.asap.domain.letter.entity.DraftLetter +import com.asap.domain.letter.entity.ReceiveDraftLetter import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.mockk.every @@ -16,14 +18,16 @@ class DraftLetterCommandServiceTest : BehaviorSpec({ val mockGenerateDraftKeyUsecase = mockk(relaxed = true) - val draftLetterCommandService = DraftLetterCommandService(mockGenerateDraftKeyUsecase) + val mockReceiveDraftLetterManagementPort = mockk(relaxed = true) + val draftLetterCommandService = + DraftLetterCommandService(mockGenerateDraftKeyUsecase, mockReceiveDraftLetterManagementPort) given("임시 저장 키를 발급할 때") { val userId = "userId" val draftLetter = DraftLetter.default(DomainId(userId)) every { mockGenerateDraftKeyUsecase.save(any()) } returns draftLetter `when`("사용자 아이디를 입력하면") { - val response = draftLetterCommandService.command(GenerateDraftKeyUsecase.Command(userId)) + val response = draftLetterCommandService.command(GenerateDraftKeyUsecase.Command.Send(userId)) then("임시 저장 키를 발급한다") { response.draftId.shouldNotBeNull() } @@ -70,4 +74,16 @@ class DraftLetterCommandServiceTest : } } } + + given("받은 편지를 임시저장하려 할때"){ + val command = GenerateDraftKeyUsecase.Command.Physical("userId") + val receiveDraftLetter = ReceiveDraftLetter.default(DomainId(command.userId)) + every { mockReceiveDraftLetterManagementPort.save(any()) } returns receiveDraftLetter + `when`("사용자 아이디를 입력하면"){ + val response = draftLetterCommandService.command(command) + then("받은 편지를 임시저장한다"){ + response.draftId.shouldNotBeNull() + } + } + } }) diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/api/DraftLetterApi.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/api/DraftLetterApi.kt index 11189ca..65bc122 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/api/DraftLetterApi.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/api/DraftLetterApi.kt @@ -31,6 +31,26 @@ interface DraftLetterApi { @AccessUser userId: String, ): GenerateDraftKeyResponse + @Operation(summary = "실물 편지 임시 저장 키 발급") + @PostMapping("/physical/key") + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "임시 저장 키 발급 성공", + content = [ + Content( + schema = + Schema(implementation = GenerateDraftKeyResponse::class), + ), + ], + ), + ], + ) + fun getPhysicalDraftKey( + @AccessUser userId: String, + ): GenerateDraftKeyResponse + @Operation(summary = "임시 저장하기") @PostMapping("/{draftId}") @ApiResponses( diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/DraftLetterController.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/DraftLetterController.kt index e9af244..a726e02 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/DraftLetterController.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/letter/controller/DraftLetterController.kt @@ -16,7 +16,12 @@ class DraftLetterController( private val removeDraftLetterUsecase: RemoveDraftLetterUsecase, ) : DraftLetterApi { override fun getDraftKey(userId: String): GenerateDraftKeyResponse { - val response = generateDraftKeyUsecase.command(GenerateDraftKeyUsecase.Command(userId)) + val response = generateDraftKeyUsecase.command(GenerateDraftKeyUsecase.Command.Send(userId)) + return GenerateDraftKeyResponse(response.draftId) + } + + override fun getPhysicalDraftKey(userId: String): GenerateDraftKeyResponse { + val response = generateDraftKeyUsecase.command(GenerateDraftKeyUsecase.Command.Physical(userId)) return GenerateDraftKeyResponse(response.draftId) } diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/DraftLetterControllerTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/DraftLetterControllerTest.kt index f2bb1cd..2dbb8e4 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/DraftLetterControllerTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/letter/controller/DraftLetterControllerTest.kt @@ -20,7 +20,7 @@ class DraftLetterControllerTest : LetterAcceptanceSupporter() { val accessToken = jwtMockManager.generateAccessToken(userId) BDDMockito - .given(generateDraftKeyUsecase.command(GenerateDraftKeyUsecase.Command(userId))) + .given(generateDraftKeyUsecase.command(GenerateDraftKeyUsecase.Command.Send(userId))) .willReturn(GenerateDraftKeyUsecase.Response("draftId")) // when @@ -180,4 +180,28 @@ class DraftLetterControllerTest : LetterAcceptanceSupporter() { status { isOk() } } } + + + @Test + fun `get physical draft key`() { + // given + val userId = userMockManager.settingUser() + val accessToken = jwtMockManager.generateAccessToken(userId) + + BDDMockito + .given(generateDraftKeyUsecase.command(GenerateDraftKeyUsecase.Command.Physical(userId))) + .willReturn(GenerateDraftKeyUsecase.Response("draftId")) + + // when + val response = + mockMvc.post("/api/v1/letters/drafts/physical/key") { + header("Authorization", "Bearer $accessToken") + } + + // then + response.andExpect { + status { isOk() } + jsonPath("$.draftId") { isString() } + } + } } diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/DraftLetterApiIntegrationTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/DraftLetterApiIntegrationTest.kt index caa3cee..0c91d47 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/DraftLetterApiIntegrationTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/letter/DraftLetterApiIntegrationTest.kt @@ -142,4 +142,22 @@ class DraftLetterApiIntegrationTest : IntegrationSupporter() { status { isOk() } } } + + @Test + fun `get physical draft key`() { + // given + val userId = userMockManager.settingUser() + val accessToken = jwtMockManager.generateAccessToken(userId) + // when + val response = + mockMvc.post("/api/v1/letters/drafts/physical/key") { + header("Authorization", "Bearer $accessToken") + } + + // then + response.andExpect { + status { isOk() } + jsonPath("$.draftId") { isString() } + } + } } diff --git a/Domain-Module/src/main/kotlin/com/asap/domain/letter/entity/ReceiveDraftLetter.kt b/Domain-Module/src/main/kotlin/com/asap/domain/letter/entity/ReceiveDraftLetter.kt new file mode 100644 index 0000000..f8c2c02 --- /dev/null +++ b/Domain-Module/src/main/kotlin/com/asap/domain/letter/entity/ReceiveDraftLetter.kt @@ -0,0 +1,42 @@ +package com.asap.domain.letter.entity + +import com.asap.domain.common.BaseEntity +import com.asap.domain.common.DomainId +import java.time.LocalDateTime + +class ReceiveDraftLetter( + id: DomainId, + var content: String, + var senderName: String, + val ownerId: DomainId, + var images: List, + var lastUpdated: LocalDateTime = LocalDateTime.now(), + val type: ReceiveDraftLetterType, +) : BaseEntity() { + companion object { + fun default(ownerId: DomainId) = + ReceiveDraftLetter( + id = DomainId.generate(), + ownerId = ownerId, + content = "", + senderName = "", + images = emptyList(), + type = ReceiveDraftLetterType.PHYSICAL, + ) + } + + fun update( + content: String, + senderName: String, + images: List, + ) { + this.content = content + this.senderName = senderName + this.images = images + this.lastUpdated = LocalDateTime.now() + } +} + +enum class ReceiveDraftLetterType{ + PHYSICAL, +} \ No newline at end of file diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/ReceiveDraftLetterMapper.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/ReceiveDraftLetterMapper.kt new file mode 100644 index 0000000..3245acc --- /dev/null +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/ReceiveDraftLetterMapper.kt @@ -0,0 +1,29 @@ +package com.asap.persistence.jpa.letter + +import com.asap.domain.common.DomainId +import com.asap.domain.letter.entity.ReceiveDraftLetter +import com.asap.persistence.jpa.letter.entity.ReceiveDraftLetterEntity + +object ReceiveDraftLetterMapper { + fun toEntity(receiveDraftLetter: ReceiveDraftLetter): ReceiveDraftLetterEntity = + ReceiveDraftLetterEntity( + id = receiveDraftLetter.id.value, + content = receiveDraftLetter.content, + senderName = receiveDraftLetter.senderName, + ownerId = receiveDraftLetter.ownerId.value, + images = receiveDraftLetter.images, + updatedAt = receiveDraftLetter.lastUpdated, + type = receiveDraftLetter.type, + ) + + fun toDomain(receiveDraftLetterEntity: ReceiveDraftLetterEntity): ReceiveDraftLetter = + ReceiveDraftLetter( + id = DomainId(receiveDraftLetterEntity.id), + content = receiveDraftLetterEntity.content, + senderName = receiveDraftLetterEntity.senderName, + ownerId = DomainId(receiveDraftLetterEntity.ownerId), + images = receiveDraftLetterEntity.images, + lastUpdated = receiveDraftLetterEntity.updatedAt, + type = receiveDraftLetterEntity.type, + ) +} \ No newline at end of file diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/adapter/ReceiveDraftLetterManagementJpaAdapter.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/adapter/ReceiveDraftLetterManagementJpaAdapter.kt new file mode 100644 index 0000000..3cb07b6 --- /dev/null +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/adapter/ReceiveDraftLetterManagementJpaAdapter.kt @@ -0,0 +1,18 @@ +package com.asap.persistence.jpa.letter.adapter + +import com.asap.application.letter.port.out.ReceiveDraftLetterManagementPort +import com.asap.domain.letter.entity.ReceiveDraftLetter +import com.asap.persistence.jpa.letter.ReceiveDraftLetterMapper +import com.asap.persistence.jpa.letter.repository.ReceiveDraftLetterJpaRepository +import org.springframework.stereotype.Repository + +@Repository +class ReceiveDraftLetterManagementJpaAdapter( + private val receiveDraftLetterJpaRepository: ReceiveDraftLetterJpaRepository, +) : ReceiveDraftLetterManagementPort { + override fun save(receiveDraftLetter: ReceiveDraftLetter): ReceiveDraftLetter { + val receiveDraftLetterEntity = ReceiveDraftLetterMapper.toEntity(receiveDraftLetter) + return receiveDraftLetterJpaRepository.save(receiveDraftLetterEntity) + .let { ReceiveDraftLetterMapper.toDomain(it) } + } +} \ No newline at end of file diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/entity/ReceiveDraftLetterEntity.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/entity/ReceiveDraftLetterEntity.kt new file mode 100644 index 0000000..6412059 --- /dev/null +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/entity/ReceiveDraftLetterEntity.kt @@ -0,0 +1,60 @@ +package com.asap.persistence.jpa.letter.entity + +import com.asap.domain.letter.entity.ReceiveDraftLetterType +import com.asap.persistence.jpa.common.BaseEntity +import com.asap.persistence.jpa.user.entity.UserEntity +import jakarta.persistence.* +import org.hibernate.annotations.JdbcTypeCode +import org.hibernate.type.SqlTypes +import java.time.LocalDateTime + +@Entity +@Table( + name = "receive_draft_letters" +) +class ReceiveDraftLetterEntity( + id: String, + content: String, + senderName: String, + ownerId: String, + images: List, + updatedAt: LocalDateTime, + type: ReceiveDraftLetterType +) : BaseEntity(id) { + + var content: String = content + var senderName: String = senderName + + @Column( + name = "owner_id", + nullable = false, + ) + var ownerId: String = ownerId + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn( + name = "owner_id", + insertable = false, + updatable = false, + ) + lateinit var owner: UserEntity + + @JdbcTypeCode(SqlTypes.JSON) + @Column( + name = "images", + nullable = false, + columnDefinition = "text", + ) + var images: List = images + + override var updatedAt: LocalDateTime = updatedAt + + @Enumerated(EnumType.STRING) + @Column( + name = "type", + nullable = false, + columnDefinition = "VARCHAR(20)", + ) + var type: ReceiveDraftLetterType = type + +} \ No newline at end of file diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/repository/ReceiveDraftLetterJpaRepository.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/repository/ReceiveDraftLetterJpaRepository.kt new file mode 100644 index 0000000..4d64f24 --- /dev/null +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/letter/repository/ReceiveDraftLetterJpaRepository.kt @@ -0,0 +1,7 @@ +package com.asap.persistence.jpa.letter.repository + +import com.asap.persistence.jpa.letter.entity.ReceiveDraftLetterEntity +import org.springframework.data.jpa.repository.JpaRepository + +interface ReceiveDraftLetterJpaRepository : JpaRepository{ +} \ No newline at end of file diff --git a/Infrastructure-Module/Persistence/src/main/resources/db/V1_15__create_receive_draft_letter.sql b/Infrastructure-Module/Persistence/src/main/resources/db/V1_15__create_receive_draft_letter.sql new file mode 100644 index 0000000..2226b8a --- /dev/null +++ b/Infrastructure-Module/Persistence/src/main/resources/db/V1_15__create_receive_draft_letter.sql @@ -0,0 +1,15 @@ +CREATE TABLE receive_draft_letters +( + id VARCHAR(255) NOT NULL, + created_at datetime NULL, + updated_at datetime NULL, + content VARCHAR(255) NULL, + sender_name VARCHAR(255) NULL, + owner_id VARCHAR(255) NOT NULL, + images TEXT NOT NULL, + type VARCHAR(20) NOT NULL, + CONSTRAINT pk_receive_draft_letters PRIMARY KEY (id) +); + +ALTER TABLE receive_draft_letters + ADD CONSTRAINT FK_RECEIVE_DRAFT_LETTERS_ON_OWNER FOREIGN KEY (owner_id) REFERENCES user (id); \ No newline at end of file