From f3d4b9e20dbdcdfa46a3eeff9b6086d5d0527986 Mon Sep 17 00:00:00 2001 From: Jimin Lim <50178026+jimin3263@users.noreply.github.com> Date: Mon, 19 Aug 2024 23:31:38 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=8F=AC=ED=82=B7=20=EB=B3=B5=EC=A0=9C?= =?UTF-8?q?=20api=20(#108)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokit/category/CategoryShareController.kt | 10 ++++++ .../dto/request/DuplicateCategoryRequest.kt | 9 +++++ .../content/impl/ContentAdapter.kt | 28 ++++++++++++++++ .../content/persist/ContentEntity.kt | 12 +++++++ .../content/persist/ContentJdbcRepository.kt | 5 +++ .../persist/ContentJdbcRepositoryImpl.kt | 33 +++++++++++++++++++ .../content/persist/ContentRepository.kt | 2 +- .../pokit/category/port/in/CategoryUseCase.kt | 1 + .../category/port/service/CategoryService.kt | 22 +++++++++++++ .../com/pokit/content/port/out/ContentPort.kt | 2 ++ .../com/pokit/category/model/Category.kt | 8 +++++ 11 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/DuplicateCategoryRequest.kt create mode 100644 adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepository.kt create mode 100644 adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepositoryImpl.kt diff --git a/adapters/in-web/src/main/kotlin/com/pokit/category/CategoryShareController.kt b/adapters/in-web/src/main/kotlin/com/pokit/category/CategoryShareController.kt index 847223a5..76898745 100644 --- a/adapters/in-web/src/main/kotlin/com/pokit/category/CategoryShareController.kt +++ b/adapters/in-web/src/main/kotlin/com/pokit/category/CategoryShareController.kt @@ -1,6 +1,7 @@ package com.pokit.category import com.pokit.auth.model.PrincipalUser +import com.pokit.category.dto.request.DuplicateCategoryRequest import com.pokit.category.dto.response.SharedContentsResponse import com.pokit.category.port.`in`.CategoryUseCase import com.pokit.common.wrapper.ResponseWrapper.wrapOk @@ -48,4 +49,13 @@ class CategoryShareController( .wrapOk() } + @Operation(summary = "포킷 복제 API") + @PostMapping + fun duplicateCategory( + @AuthenticationPrincipal user: PrincipalUser, + @RequestBody request: DuplicateCategoryRequest, + ): ResponseEntity = + categoryUseCase.duplicateCategory(request.originCategoryId, request.categoryName, user.id) + .wrapOk() + } diff --git a/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/DuplicateCategoryRequest.kt b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/DuplicateCategoryRequest.kt new file mode 100644 index 00000000..984bdfcf --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/DuplicateCategoryRequest.kt @@ -0,0 +1,9 @@ +package com.pokit.category.dto.request + +import jakarta.validation.constraints.Size + +data class DuplicateCategoryRequest ( + val originCategoryId: Long, + @field:Size(min = 1, max = 10, message = "최대 10자까지 입력 가능합니다.") + val categoryName: String, +) diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/impl/ContentAdapter.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/impl/ContentAdapter.kt index 5eb0b74f..ad437dad 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/impl/ContentAdapter.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/impl/ContentAdapter.kt @@ -179,10 +179,38 @@ class ContentAdapter( return SliceImpl(contentResults, pageable, hasNext) } + override fun duplicateContent(originCategoryId: Long, targetCategoryId: Long) { + val contents = loadByCategoryIdAndOpenType(originCategoryId, OpenType.PUBLIC) + + val targetContentEntities = contents.map { + ContentEntity.from(it, targetCategoryId) + } + + contentRepository.bulkInsert(targetContentEntities) + } + override fun loadByContentIds(contentIds: List): List = contentRepository.findByIdIn(contentIds) .map { it.toDomain() } + private fun loadByCategoryIdAndOpenType(categoryId: Long, opentype: OpenType): List { + val contentEntities = queryFactory.select(contentEntity) + .from(contentEntity) + .join(categoryEntity).on(categoryEntity.id.eq(contentEntity.categoryId)) + .where( + categoryEntity.id.eq(categoryId), + categoryEntity.openType.eq(opentype), + contentEntity.deleted.isFalse, + ) + .fetch() + + val contents = contentEntities.map { + it.toDomain() + } + + return contents + } + private fun getHasNext( items: MutableList, pageable: Pageable, diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentEntity.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentEntity.kt index f07d9113..e43a0522 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentEntity.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentEntity.kt @@ -57,6 +57,18 @@ class ContentEntity( domain = content.domain, thumbNail = content.thumbNail ) + + fun from(content: Content, categoryId: Long) = ContentEntity( + id = content.id, + categoryId = categoryId, + type = content.type, + data = content.data, + title = content.title, + memo = content.memo, + alertYn = "NO", + domain = content.domain, + thumbNail = content.thumbNail + ) } } diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepository.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepository.kt new file mode 100644 index 00000000..9a1d155a --- /dev/null +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepository.kt @@ -0,0 +1,5 @@ +package com.pokit.out.persistence.content.persist + +interface ContentJdbcRepository { + fun bulkInsert(contentEntity: List) +} diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepositoryImpl.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepositoryImpl.kt new file mode 100644 index 00000000..ed58298f --- /dev/null +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentJdbcRepositoryImpl.kt @@ -0,0 +1,33 @@ +package com.pokit.out.persistence.content.persist + +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.stereotype.Repository + +@Repository +class ContentJdbcRepositoryImpl( + private val jdbcTemplate: JdbcTemplate +) : ContentJdbcRepository { + override fun bulkInsert(contentEntities: List) { + val sql = """ + INSERT INTO content ( + category_id, type, data, title, memo, alert_yn, domain, is_deleted, thumb_nail, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + """.trimIndent() + + val batchArgs = contentEntities.map { content -> + arrayOf( + content.categoryId, + content.type.name, + content.data, + content.title, + content.memo, + content.alertYn, + content.domain, + content.deleted, + content.thumbNail + ) + } + + jdbcTemplate.batchUpdate(sql, batchArgs) + } +} diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentRepository.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentRepository.kt index 3b6cd9c6..39c46273 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentRepository.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/content/persist/ContentRepository.kt @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param -interface ContentRepository : JpaRepository { +interface ContentRepository : JpaRepository, ContentJdbcRepository { @Query( """ select co from ContentEntity co diff --git a/application/src/main/kotlin/com/pokit/category/port/in/CategoryUseCase.kt b/application/src/main/kotlin/com/pokit/category/port/in/CategoryUseCase.kt index aab18577..2555efbc 100644 --- a/application/src/main/kotlin/com/pokit/category/port/in/CategoryUseCase.kt +++ b/application/src/main/kotlin/com/pokit/category/port/in/CategoryUseCase.kt @@ -17,4 +17,5 @@ interface CategoryUseCase { fun getCategory(userId: Long, categoryId: Long): Category fun getSharedCategory(categoryId: Long, userId: Long): Category fun completeShare(categoryId: Long, userId: Long) + fun duplicateCategory(originCategoryId: Long, categoryName: String, userId: Long) } diff --git a/application/src/main/kotlin/com/pokit/category/port/service/CategoryService.kt b/application/src/main/kotlin/com/pokit/category/port/service/CategoryService.kt index 9db1e246..088bc352 100644 --- a/application/src/main/kotlin/com/pokit/category/port/service/CategoryService.kt +++ b/application/src/main/kotlin/com/pokit/category/port/service/CategoryService.kt @@ -8,6 +8,7 @@ import com.pokit.category.model.Category import com.pokit.category.model.CategoryImage import com.pokit.category.model.CategoryStatus.UNCATEGORIZED import com.pokit.category.model.OpenType +import com.pokit.category.model.duplicate import com.pokit.category.port.`in`.CategoryUseCase import com.pokit.category.port.out.CategoryImagePort import com.pokit.category.port.out.CategoryPort @@ -126,6 +127,27 @@ class CategoryService( categoryPort.persist(category) } + @Transactional + override fun duplicateCategory(originCategoryId: Long, categoryName: String, userId: Long) { + val originCategory = categoryPort.loadByIdAndOpenType(originCategoryId, OpenType.PUBLIC) + ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY) + + if (originCategory.userId == userId) { + throw InvalidRequestException(CategoryErrorCode.SHARE_ALREADY_EXISTS_CATEGORY) + } + + if (categoryPort.countByUserId(userId) >= MAX_CATEGORY_COUNT) { + throw InvalidRequestException(CategoryErrorCode.SHARE_MAX_CATEGORY_LIMIT_EXCEEDED) + } + + if (categoryPort.existsByNameAndUserId(originCategory.categoryName, userId)) { + throw AlreadyExistsException(CategoryErrorCode.SHARE_ALREADY_EXISTS_CATEGORY_NAME) + } + + val newCategory = categoryPort.persist(originCategory.duplicate(categoryName, userId)) + contentPort.duplicateContent(originCategoryId, newCategory.categoryId) + } + override fun getAllCategoryImages(): List = categoryImagePort.loadAll() diff --git a/application/src/main/kotlin/com/pokit/content/port/out/ContentPort.kt b/application/src/main/kotlin/com/pokit/content/port/out/ContentPort.kt index e83199e0..6214f24f 100644 --- a/application/src/main/kotlin/com/pokit/content/port/out/ContentPort.kt +++ b/application/src/main/kotlin/com/pokit/content/port/out/ContentPort.kt @@ -42,4 +42,6 @@ interface ContentPort { opentype: OpenType, pageable: Pageable ): Slice + + fun duplicateContent(originCategoryId: Long, targetCategoryId: Long) } diff --git a/domain/src/main/kotlin/com/pokit/category/model/Category.kt b/domain/src/main/kotlin/com/pokit/category/model/Category.kt index 64bb8808..7c9f611e 100644 --- a/domain/src/main/kotlin/com/pokit/category/model/Category.kt +++ b/domain/src/main/kotlin/com/pokit/category/model/Category.kt @@ -31,3 +31,11 @@ fun Category.toRemindCategory() = RemindCategory( categoryId = this.categoryId, categoryName = this.categoryName, ) + +fun Category.duplicate(newCategoryName: String, userId: Long): Category { + return this.copy( + categoryId = 0L, + userId = userId, + categoryName = newCategoryName + ) +}