Skip to content

Commit

Permalink
[feat #21] 북마크 등록 API(콕하기) (#22)
Browse files Browse the repository at this point in the history
* feat : 컨텐츠, 북마크 엔티티 메소드 추가

* feat : 컨텐츠, 북마크 도메인 모델

* feat : 북마크 영속성 계층 구현

* feat : 컨텐츠 영속성 계층 구현

* feat : 북마크 등록 비즈니스 로직

* feat : 북마크 등록 api

* feat : 북마크 관련 fixture, errorcode정의

* feat : 컨텐츠 조회 쿼리 수정

* feat : 컨텐츠 조회 쿼리 수정에 따른 테스트코드 수정

* feat : 컨텐츠 조회 쿼리 수정에 따른 코드 수정

* feat : TestContainer 설정 수정 및 불필요 파일 제거
  • Loading branch information
dlswns2480 authored Jul 22, 2024
1 parent 51cb9d9 commit 3034e3d
Show file tree
Hide file tree
Showing 22 changed files with 285 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.pokit.content

import com.pokit.auth.model.PrincipalUser
import com.pokit.auth.model.toDomain
import com.pokit.content.dto.response.BookMarkContentResponse
import com.pokit.content.port.`in`.ContentUseCase
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v1/content/")
class ContentController(
private val contentUseCase: ContentUseCase
) {
@PostMapping("/{contentId}/bookmark")
fun bookmarkContent(
@AuthenticationPrincipal principalUser: PrincipalUser,
@PathVariable("contentId") contentId: Long
): ResponseEntity<BookMarkContentResponse> {
val user = principalUser.toDomain()
val response = contentUseCase.bookmarkContent(user, contentId = contentId)
return ResponseEntity.ok(response)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.pokit.out.persistence.bookmark.impl

import com.pokit.bookmark.model.Bookmark
import com.pokit.bookmark.port.out.BookmarkPort
import com.pokit.out.persistence.bookmark.persist.BookMarkRepository
import com.pokit.out.persistence.bookmark.persist.BookmarkEntity
import com.pokit.out.persistence.bookmark.persist.toDomain
import org.springframework.stereotype.Repository

@Repository
class BookMarkAdapter(
private val bookMarkRepository: BookMarkRepository
) : BookmarkPort {
override fun persist(bookmark: Bookmark): Bookmark {
val bookmarkEntity = BookmarkEntity.of(bookmark)
val savedBookmark = bookMarkRepository.save(bookmarkEntity)
return savedBookmark.toDomain()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pokit.out.persistence.bookmark.persist

import org.springframework.data.jpa.repository.JpaRepository

interface BookMarkRepository : JpaRepository<BookmarkEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pokit.out.persistence.bookmark.persist

import com.pokit.bookmark.model.Bookmark
import jakarta.persistence.*

// 콕 엔티티
Expand All @@ -16,4 +17,17 @@ class BookmarkEntity(

@Column(name = "user_id")
val userId: Long,
) {
companion object {
fun of(bookmark: Bookmark) =
BookmarkEntity(
contentId = bookmark.contentId,
userId = bookmark.userId
)
}
}

fun BookmarkEntity.toDomain() = Bookmark(
contentId = this.contentId,
userId = this.userId
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.pokit.out.persistence.content.impl

import com.pokit.content.port.out.ContentPort
import com.pokit.out.persistence.content.persist.ContentRepository
import com.pokit.out.persistence.content.persist.toDomain
import org.springframework.stereotype.Repository

@Repository
class ContenAdapter(
private val contentRepository: ContentRepository
) : ContentPort {
override fun loadByUserIdAndId(userId: Long, id: Long) = contentRepository.findByUserIdAndId(userId, id)
?.run { toDomain() }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pokit.out.persistence.content.persist

import com.pokit.content.model.Content
import com.pokit.content.model.ContentType
import com.pokit.out.persistence.BaseEntity
import jakarta.persistence.*
Expand Down Expand Up @@ -31,3 +32,12 @@ class ContentEntity(
@Column(name = "alert_yn")
val alertYn: String,
) : BaseEntity()

fun ContentEntity.toDomain() = Content(
categoryId = this.categoryId,
type = this.type,
data = this.data,
title = this.title,
memo = this.memo,
alertYn = this.alertYn,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.pokit.out.persistence.content.persist

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

interface ContentRepository : JpaRepository<ContentEntity, Long> {
@Query(
"""
select co from ContentEntity co
join CategoryEntity ca on co.categoryId = ca.id
join UserEntity u on ca.userId = :userId
where co.id = :id
"""
)
fun findByUserIdAndId(
@Param("userId") userId: Long,
@Param("id") id: Long
): ContentEntity?
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.pokit.bookmark

import com.pokit.bookmark.model.Bookmark

class BookmarkFixture {
companion object {
fun getBookmark(contentId: Long, userId: Long) = Bookmark(
contentId = contentId,
userId = userId
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.pokit.content

import com.pokit.content.model.Content
import com.pokit.content.model.ContentType

class ContentFixture {
companion object {
fun getContent() = Content(
categoryId = 1L,
type = ContentType.LINK,
data = "blahblah.com",
title = "어떤 제목",
memo = "이러한 내용 요약",
alertYn = "YES"
)
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
package com.pokit.support

import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.JdbcDatabaseContainer
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.context.ApplicationContextInitializer
import org.springframework.context.ConfigurableApplicationContext
import org.testcontainers.containers.MySQLContainer
import org.testcontainers.utility.DockerImageName

abstract class TestContainerSupport {
class TestContainerSupport : ApplicationContextInitializer<ConfigurableApplicationContext> {
companion object {
private const val MYSQL_IMAGE = "mysql:8.0"

private const val MYSQL_PORT = 3306

private val MYSQL: JdbcDatabaseContainer<*> =
MySQLContainer<Nothing>(DockerImageName.parse(MYSQL_IMAGE))
.withExposedPorts(MYSQL_PORT)

init {
MYSQL.start()
val mysqlContainer: MySQLContainer<*> = MySQLContainer("mysql:8.0").apply {
withExposedPorts(3306)
start()
}
}

@JvmStatic
@DynamicPropertySource
fun overrideProps(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.driver-class-name") { MYSQL.driverClassName }
registry.add("spring.datasource.url") { MYSQL.jdbcUrl }
registry.add("spring.datasource.username") { MYSQL.username }
registry.add("spring.datasource.password") { MYSQL.password }
}
override fun initialize(applicationContext: ConfigurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=${mysqlContainer.jdbcUrl}",
"spring.datasource.username=${mysqlContainer.username}",
"spring.datasource.password=${mysqlContainer.password}",
"spring.jpa.hibernate.ddl-auto=create"
).applyTo(applicationContext.environment)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.pokit.bookmark.port.out

import com.pokit.bookmark.model.Bookmark

interface BookmarkPort {
fun persist(bookmark: Bookmark): Bookmark
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.pokit.content.port.`in`

import com.pokit.content.dto.response.BookMarkContentResponse
import com.pokit.user.model.User

interface ContentUseCase {
fun bookmarkContent(user: User, contentId: Long): BookMarkContentResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.pokit.content.port.out

import com.pokit.content.model.Content

interface ContentPort {
fun loadByUserIdAndId(userId: Long, id: Long): Content?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.pokit.content.port.service

import com.pokit.bookmark.model.Bookmark
import com.pokit.bookmark.port.out.BookmarkPort
import com.pokit.common.exception.NotFoundCustomException
import com.pokit.content.dto.response.BookMarkContentResponse
import com.pokit.content.exception.ContentErrorCode
import com.pokit.content.model.Content
import com.pokit.content.port.`in`.ContentUseCase
import com.pokit.content.port.out.ContentPort
import com.pokit.user.model.User
import org.springframework.stereotype.Service

@Service
class ContentService(
private val contentPort: ContentPort,
private val bookMarkPort: BookmarkPort
) : ContentUseCase {
override fun bookmarkContent(user: User, contentId: Long): BookMarkContentResponse {
verifyContent(user.id, contentId)
val bookmark = Bookmark(userId = user.id, contentId = contentId)
val savedBookmark = bookMarkPort.persist(bookmark)
return BookMarkContentResponse(savedBookmark.contentId)
}

private fun verifyContent(userId: Long, contentId: Long): Content {
return contentPort.loadByUserIdAndId(userId, contentId)
?: throw NotFoundCustomException(ContentErrorCode.NOT_FOUND_CONTENT)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.pokit.content.port.service

import com.pokit.bookmark.BookmarkFixture
import com.pokit.bookmark.port.out.BookmarkPort
import com.pokit.common.exception.NotFoundCustomException
import com.pokit.content.ContentFixture
import com.pokit.content.port.out.ContentPort
import com.pokit.user.UserFixture
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk

class ContentServiceTest : BehaviorSpec({
val contentPort = mockk<ContentPort>()
val bookmarkPort = mockk<BookmarkPort>()
val contentService = ContentService(contentPort, bookmarkPort)

Given("컨텐츠에 대해 즐겨찾기 할 때") {
val user = UserFixture.getUser()
val requestContentId = 1L
val invalidContentId = 2L
val content = ContentFixture.getContent()
val bookmark = BookmarkFixture.getBookmark(requestContentId, user.id)

every { contentPort.loadByUserIdAndId(user.id, requestContentId) } returns content
every { contentPort.loadByUserIdAndId(user.id, invalidContentId) } returns null
every { bookmarkPort.persist(bookmark) } returns bookmark

When("존재하지 않는 컨텐츠면") {
Then("예외가 발생한다.") {
shouldThrow<NotFoundCustomException> {
contentService.bookmarkContent(user, invalidContentId)
}
}
}

When("존재하는 컨텐츠면") {
val response = contentService.bookmarkContent(user, requestContentId)
Then("북마크 처리 후 컨텐츠 아이디가 반환된다.") {
response.contentId shouldBe requestContentId
}
}
}
})
6 changes: 6 additions & 0 deletions domain/src/main/kotlin/com/pokit/bookmark/model/Bookmark.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.pokit.bookmark.model

data class Bookmark(
val contentId: Long,
val userId: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.pokit.content.dto.response

data class BookMarkContentResponse(
val contentId: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.pokit.content.exception

import com.pokit.common.exception.ErrorCode

enum class ContentErrorCode(
override val message: String,
override val code: String
) : ErrorCode {
NOT_FOUND_CONTENT("존재하지 않는 컨텐츠입니다.", "C_001")
}
10 changes: 10 additions & 0 deletions domain/src/main/kotlin/com/pokit/content/model/Content.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.pokit.content.model

data class Content(
val categoryId: Long,
val type: ContentType,
val data: String,
val title: String,
val memo: String,
val alertYn: String,
)

0 comments on commit 3034e3d

Please sign in to comment.