From f0a3fbd9e21ac9618fd2601a902f0b01493f5877 Mon Sep 17 00:00:00 2001 From: San Kim Date: Mon, 22 Apr 2024 16:02:54 +0900 Subject: [PATCH] =?UTF-8?q?[WEAV-340]=20=EC=A0=9C=EC=95=88=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20API=20=EA=B0=9C=EB=B0=9C=20(#256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/inbound/CreateSuggestion.kt | 13 +++++ .../port/outbound/SuggestionRepository.kt | 9 ++++ .../service/CreateSuggestionService.kt | 22 +++++++++ .../service/CreateSuggestionServiceTest.kt | 39 +++++++++++++++ .../outbound/SuggestionRepositorySpy.kt | 22 +++++++++ .../bootstrap/suggestion/api/SuggestionApi.kt | 26 ++++++++++ .../controller/SuggestionRestController.kt | 20 ++++++++ .../suggestion/dto/SuggestionCreateRequest.kt | 20 ++++++++ .../domain/suggestion/entity/Suggestion.kt | 39 +++++++++++++++ .../adapter/SuggestionJpaAdapter.kt | 19 ++++++++ .../suggestion/entity/SuggestionJpaEntity.kt | 47 +++++++++++++++++++ .../repository/SuggestionJpaRepository.kt | 9 ++++ .../db/migration/V22__create_suggestion.sql | 7 +++ 13 files changed, 292 insertions(+) create mode 100644 application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/inbound/CreateSuggestion.kt create mode 100644 application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/outbound/SuggestionRepository.kt create mode 100644 application/src/main/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionService.kt create mode 100644 application/src/test/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionServiceTest.kt create mode 100644 application/src/testFixtures/kotlin/com/studentcenter/weave/application/suggestion/outbound/SuggestionRepositorySpy.kt create mode 100644 bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/api/SuggestionApi.kt create mode 100644 bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/controller/SuggestionRestController.kt create mode 100644 bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/dto/SuggestionCreateRequest.kt create mode 100644 domain/src/main/kotlin/com/studentcenter/weave/domain/suggestion/entity/Suggestion.kt create mode 100644 infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/adapter/SuggestionJpaAdapter.kt create mode 100644 infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/entity/SuggestionJpaEntity.kt create mode 100644 infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/repository/SuggestionJpaRepository.kt create mode 100644 infrastructure/persistence/src/main/resources/db/migration/V22__create_suggestion.sql diff --git a/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/inbound/CreateSuggestion.kt b/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/inbound/CreateSuggestion.kt new file mode 100644 index 00000000..5c844073 --- /dev/null +++ b/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/inbound/CreateSuggestion.kt @@ -0,0 +1,13 @@ +package com.studentcenter.weave.application.suggestion.port.inbound + +import java.util.* + +interface CreateSuggestion { + + fun invoke(command: Command) + + data class Command( + val userId: UUID, + val contents: String, + ) +} diff --git a/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/outbound/SuggestionRepository.kt b/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/outbound/SuggestionRepository.kt new file mode 100644 index 00000000..19ea121d --- /dev/null +++ b/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/port/outbound/SuggestionRepository.kt @@ -0,0 +1,9 @@ +package com.studentcenter.weave.application.suggestion.port.outbound + +import com.studentcenter.weave.domain.suggestion.entity.Suggestion + +interface SuggestionRepository { + + fun save(suggestion: Suggestion) + +} diff --git a/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionService.kt b/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionService.kt new file mode 100644 index 00000000..658e855c --- /dev/null +++ b/application/src/main/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionService.kt @@ -0,0 +1,22 @@ +package com.studentcenter.weave.application.suggestion.service + +import com.studentcenter.weave.application.suggestion.port.inbound.CreateSuggestion +import com.studentcenter.weave.application.suggestion.port.outbound.SuggestionRepository +import com.studentcenter.weave.domain.suggestion.entity.Suggestion +import org.springframework.stereotype.Service + +@Service +class CreateSuggestionService( + private val suggestionRepository: SuggestionRepository, +) : CreateSuggestion { + + override fun invoke(command: CreateSuggestion.Command) { + Suggestion.create( + userId = command.userId, + contents = command.contents + ).also { + suggestionRepository.save(it) + } + } + +} diff --git a/application/src/test/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionServiceTest.kt b/application/src/test/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionServiceTest.kt new file mode 100644 index 00000000..7f316768 --- /dev/null +++ b/application/src/test/kotlin/com/studentcenter/weave/application/suggestion/service/CreateSuggestionServiceTest.kt @@ -0,0 +1,39 @@ +package com.studentcenter.weave.application.suggestion.service + +import com.studentcenter.weave.application.suggestion.outbound.SuggestionRepositorySpy +import com.studentcenter.weave.application.suggestion.port.inbound.CreateSuggestion +import com.studentcenter.weave.domain.user.entity.UserFixtureFactory +import io.kotest.core.annotation.DisplayName +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe + +@DisplayName("CreateSuggestionService") +class CreateSuggestionServiceTest : DescribeSpec({ + + val suggestionRepository = SuggestionRepositorySpy() + val sut = CreateSuggestionService(suggestionRepository) + + afterEach { + suggestionRepository.clear() + } + + describe("제안 생성") { + it("제안을 생성하고 DB에 저장한다") { + // arrange + val userFixture = UserFixtureFactory.create() + + val command = CreateSuggestion.Command( + userId = userFixture.id, + contents = "contents" + ) + + // act + sut.invoke(command) + + // assert + suggestionRepository.count() shouldBe 1 + } + } + + +}) diff --git a/application/src/testFixtures/kotlin/com/studentcenter/weave/application/suggestion/outbound/SuggestionRepositorySpy.kt b/application/src/testFixtures/kotlin/com/studentcenter/weave/application/suggestion/outbound/SuggestionRepositorySpy.kt new file mode 100644 index 00000000..24764d5d --- /dev/null +++ b/application/src/testFixtures/kotlin/com/studentcenter/weave/application/suggestion/outbound/SuggestionRepositorySpy.kt @@ -0,0 +1,22 @@ +package com.studentcenter.weave.application.suggestion.outbound + +import com.studentcenter.weave.application.suggestion.port.outbound.SuggestionRepository +import com.studentcenter.weave.domain.suggestion.entity.Suggestion +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +class SuggestionRepositorySpy : SuggestionRepository { + + private val bucket = ConcurrentHashMap() + + override fun save(suggestion: Suggestion) { + bucket[suggestion.id] = suggestion + } + + fun clear() { + bucket.clear() + } + + fun count() = bucket.size + +} diff --git a/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/api/SuggestionApi.kt b/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/api/SuggestionApi.kt new file mode 100644 index 00000000..a1e06410 --- /dev/null +++ b/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/api/SuggestionApi.kt @@ -0,0 +1,26 @@ +package com.studentcenter.weave.bootstrap.suggestion.api + +import com.studentcenter.weave.bootstrap.common.security.annotation.Secured +import com.studentcenter.weave.bootstrap.suggestion.dto.SuggestionCreateRequest +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.ResponseStatus + +@Tag(name = "Suggestion", description = "Suggestion API") +@RequestMapping("/api/suggestions") +interface SuggestionApi { + + @Secured + @Operation(summary = "Create new suggestion") + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + fun createSuggestion( + @RequestBody + request: SuggestionCreateRequest, + ) + +} diff --git a/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/controller/SuggestionRestController.kt b/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/controller/SuggestionRestController.kt new file mode 100644 index 00000000..e520e01d --- /dev/null +++ b/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/controller/SuggestionRestController.kt @@ -0,0 +1,20 @@ +package com.studentcenter.weave.bootstrap.suggestion.controller + +import com.studentcenter.weave.application.common.security.context.getCurrentUserAuthentication +import com.studentcenter.weave.application.suggestion.port.inbound.CreateSuggestion +import com.studentcenter.weave.bootstrap.suggestion.api.SuggestionApi +import com.studentcenter.weave.bootstrap.suggestion.dto.SuggestionCreateRequest +import org.springframework.web.bind.annotation.RestController + +@RestController +class SuggestionRestController( + private val createSuggestion: CreateSuggestion, +) : SuggestionApi { + + override fun createSuggestion(request: SuggestionCreateRequest) { + request + .toCommand(getCurrentUserAuthentication()) + .also { createSuggestion.invoke(it) } + } + +} diff --git a/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/dto/SuggestionCreateRequest.kt b/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/dto/SuggestionCreateRequest.kt new file mode 100644 index 00000000..d905a752 --- /dev/null +++ b/bootstrap/http/src/main/kotlin/com/studentcenter/weave/bootstrap/suggestion/dto/SuggestionCreateRequest.kt @@ -0,0 +1,20 @@ +package com.studentcenter.weave.bootstrap.suggestion.dto + +import com.studentcenter.weave.application.suggestion.port.inbound.CreateSuggestion +import com.studentcenter.weave.application.user.vo.UserAuthentication + +data class SuggestionCreateRequest( + val contents: String?, +) { + + fun toCommand(userAuth: UserAuthentication): CreateSuggestion.Command { + require(contents != null) { + "내용을 입력해 주세요!" + } + + return CreateSuggestion.Command( + userId = userAuth.userId, + contents = contents + ) + } +} diff --git a/domain/src/main/kotlin/com/studentcenter/weave/domain/suggestion/entity/Suggestion.kt b/domain/src/main/kotlin/com/studentcenter/weave/domain/suggestion/entity/Suggestion.kt new file mode 100644 index 00000000..528c15ce --- /dev/null +++ b/domain/src/main/kotlin/com/studentcenter/weave/domain/suggestion/entity/Suggestion.kt @@ -0,0 +1,39 @@ +package com.studentcenter.weave.domain.suggestion.entity + +import com.studentcenter.weave.domain.common.AggregateRoot +import com.studentcenter.weave.support.common.uuid.UuidCreator +import java.util.* + +data class Suggestion( + override val id: UUID = UuidCreator.create(), + val userId: UUID, + val contents: String, +) : AggregateRoot { + + init { + require(contents.isNotBlank()) { + "내용을 입력해 주세요!" + } + + require(contents.length <= MAX_CONTENTS_LENGTH) { + "내용은 ${MAX_CONTENTS_LENGTH}자 이하로 입력해 주세요!" + } + } + + companion object { + + const val MAX_CONTENTS_LENGTH = 2000 + + fun create( + userId: UUID, + contents: String, + ): Suggestion { + return Suggestion( + userId = userId, + contents = contents + ) + } + + } + +} diff --git a/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/adapter/SuggestionJpaAdapter.kt b/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/adapter/SuggestionJpaAdapter.kt new file mode 100644 index 00000000..81711f33 --- /dev/null +++ b/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/adapter/SuggestionJpaAdapter.kt @@ -0,0 +1,19 @@ +package com.studentcenter.weave.infrastructure.persistence.suggestion.adapter + +import com.studentcenter.weave.application.suggestion.port.outbound.SuggestionRepository +import com.studentcenter.weave.domain.suggestion.entity.Suggestion +import com.studentcenter.weave.infrastructure.persistence.suggestion.entity.SuggestionJpaEntity.Companion.toJpaEntity +import com.studentcenter.weave.infrastructure.persistence.suggestion.repository.SuggestionJpaRepository +import org.springframework.stereotype.Component + +@Component +class SuggestionJpaAdapter( + private val suggestionJpaRepository: SuggestionJpaRepository, +) : SuggestionRepository { + + override fun save(suggestion: Suggestion) { + suggestionJpaRepository.save(suggestion.toJpaEntity()) + } + + +} diff --git a/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/entity/SuggestionJpaEntity.kt b/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/entity/SuggestionJpaEntity.kt new file mode 100644 index 00000000..c6da6b1f --- /dev/null +++ b/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/entity/SuggestionJpaEntity.kt @@ -0,0 +1,47 @@ +package com.studentcenter.weave.infrastructure.persistence.suggestion.entity + +import com.studentcenter.weave.domain.suggestion.entity.Suggestion +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.util.* + +@Entity +@Table(name = "suggestion") +class SuggestionJpaEntity( + id: UUID, + userId: UUID, + contents: String, +) { + + @Id + @Column(name = "id", nullable = false, updatable = false, columnDefinition = "BINARY(16)") + var id: UUID = id + private set + + @Column(name = "user_id", nullable = false, updatable = false, columnDefinition = "BINARY(16)") + var userId: UUID = userId + private set + + @Column(name = "contents", nullable = false, length = 2000, columnDefinition = "TEXT") + var contents: String = contents + private set + + fun toDomainEntity() = Suggestion( + id = id, + userId = userId, + contents = contents + ) + + companion object { + + fun Suggestion.toJpaEntity() = SuggestionJpaEntity( + id = id, + userId = userId, + contents = contents + ) + + } + +} diff --git a/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/repository/SuggestionJpaRepository.kt b/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/repository/SuggestionJpaRepository.kt new file mode 100644 index 00000000..47e539b5 --- /dev/null +++ b/infrastructure/persistence/src/main/kotlin/com/studentcenter/weave/infrastructure/persistence/suggestion/repository/SuggestionJpaRepository.kt @@ -0,0 +1,9 @@ +package com.studentcenter.weave.infrastructure.persistence.suggestion.repository + +import com.studentcenter.weave.infrastructure.persistence.suggestion.entity.SuggestionJpaEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository +import java.util.* + +@Repository +interface SuggestionJpaRepository : JpaRepository diff --git a/infrastructure/persistence/src/main/resources/db/migration/V22__create_suggestion.sql b/infrastructure/persistence/src/main/resources/db/migration/V22__create_suggestion.sql new file mode 100644 index 00000000..9fc7d180 --- /dev/null +++ b/infrastructure/persistence/src/main/resources/db/migration/V22__create_suggestion.sql @@ -0,0 +1,7 @@ +create table suggestion +( + id binary(16) primary key, + user_id binary(16) not null, + contents text not null +) engine = InnoDB, + char set utf8mb4;