diff --git a/adapters/in-web/src/main/kotlin/com/pokit/content/ContentController.kt b/adapters/in-web/src/main/kotlin/com/pokit/content/ContentController.kt index 6e8deb5a..0ca8feb2 100644 --- a/adapters/in-web/src/main/kotlin/com/pokit/content/ContentController.kt +++ b/adapters/in-web/src/main/kotlin/com/pokit/content/ContentController.kt @@ -181,5 +181,16 @@ class ContentController( .wrapUnit() } + @PatchMapping + @Operation(summary = "미분류 링크 포킷으로 이동 API") + fun categorizeContents( + @AuthenticationPrincipal user: PrincipalUser, + @RequestBody request: CategorizeRequest + ): ResponseEntity { + return contentUseCase.categorize(user.id, request.toDto()) + .wrapUnit() + } + + } diff --git a/adapters/in-web/src/main/kotlin/com/pokit/content/dto/request/CategorizeRequest.kt b/adapters/in-web/src/main/kotlin/com/pokit/content/dto/request/CategorizeRequest.kt new file mode 100644 index 00000000..b8541288 --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/content/dto/request/CategorizeRequest.kt @@ -0,0 +1,16 @@ +package com.pokit.content.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotNull + +data class CategorizeRequest( + val contentIds: List, + @Schema(description = "이동시키려는 카테고리 ID") + @NotNull(message = "카테고리 ID는 필수값입니다.") + val categoryId: Long +) + +internal fun CategorizeRequest.toDto() = CategorizeCommand( + contentIds = this.contentIds, + categoryId = this.categoryId +) 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 dd7c8601..2d7bab7e 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 @@ -204,6 +204,16 @@ class ContentAdapter( contentRepository.deleteByContentIds(contentIds) } + override fun loadAllByUserIdAndContentIds(userId: Long, contentIds: List): List { + return contentRepository.findAllByUserIdAndContentIds(userId, contentIds) + .map { it.toDomain() } + } + + override fun updateCategoryId(contents: List, categoryId: Long) { + val contentIds = contents.map { it.id } + contentRepository.updateCategoryId(contentIds, categoryId) + } + override fun loadByContentIds(contentIds: List): List = contentRepository.findByIdIn(contentIds) .map { it.toDomain() } 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 ecfccac9..d86876f4 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 @@ -62,4 +62,29 @@ interface ContentRepository : JpaRepository, ContentJdbcRep """ ) fun deleteByContentIds(@Param("contentIds") contentIds: List) + + @Query( + """ + select c from ContentEntity c + join CategoryEntity ca on ca.id = c.categoryId + join UserEntity u on u.id = ca.userId + where u.id = :userId and c.id in :contentIds and c.deleted = false + """ + ) + fun findAllByUserIdAndContentIds( + @Param("userId") userId: Long, + @Param("contentIds") contentIds: List + ): List + + @Modifying(clearAutomatically = true) + @Query( + """ + update ContentEntity c set c.categoryId = :categoryId + where c.id in :contentIds + """ + ) + fun updateCategoryId( + @Param("contentIds") contentIds: List, + @Param("categoryId") categoryId: Long + ) } diff --git a/application/src/main/kotlin/com/pokit/content/port/in/ContentUseCase.kt b/application/src/main/kotlin/com/pokit/content/port/in/ContentUseCase.kt index f6b98baf..10db9be7 100644 --- a/application/src/main/kotlin/com/pokit/content/port/in/ContentUseCase.kt +++ b/application/src/main/kotlin/com/pokit/content/port/in/ContentUseCase.kt @@ -1,5 +1,6 @@ package com.pokit.content.port.`in` +import com.pokit.content.dto.request.CategorizeCommand import com.pokit.content.dto.request.ContentCommand import com.pokit.content.dto.request.ContentSearchCondition import com.pokit.content.dto.response.* @@ -42,4 +43,6 @@ interface ContentUseCase { fun getBookmarkCount(userId: Long): Int fun deleteUncategorized(userId: Long, contentIds: List) + + fun categorize(userId: Long, command: CategorizeCommand) } 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 ce4d9c5b..281c8573 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 @@ -48,4 +48,8 @@ interface ContentPort { fun loadByContentIdsWithUser(contetIds: List): List fun deleteAllByIds(contentIds: List) + + fun loadAllByUserIdAndContentIds(userId: Long, contentIds: List): List + + fun updateCategoryId(contents: List, categoryId: Long) } diff --git a/application/src/main/kotlin/com/pokit/content/port/service/ContentService.kt b/application/src/main/kotlin/com/pokit/content/port/service/ContentService.kt index 473b568c..158a6773 100644 --- a/application/src/main/kotlin/com/pokit/content/port/service/ContentService.kt +++ b/application/src/main/kotlin/com/pokit/content/port/service/ContentService.kt @@ -13,6 +13,7 @@ import com.pokit.category.port.service.loadCategoryOrThrow import com.pokit.common.exception.AlreadyExistsException import com.pokit.common.exception.ClientValidationException import com.pokit.common.exception.NotFoundCustomException +import com.pokit.content.dto.request.CategorizeCommand import com.pokit.content.dto.request.ContentCommand import com.pokit.content.dto.request.ContentSearchCondition import com.pokit.content.dto.request.toDomain @@ -181,6 +182,13 @@ class ContentService( contentPort.deleteAllByIds(contentIds) } + @Transactional + override fun categorize(userId: Long, command: CategorizeCommand) { + val category = verifyCategory(command.categoryId, userId) + val contents = contentPort.loadAllByUserIdAndContentIds(userId, command.contentIds) + contentPort.updateCategoryId(contents, category.categoryId) + } + private fun verifyContent(userId: Long, contentId: Long): Content { return contentPort.loadByUserIdAndId(userId, contentId) ?: throw NotFoundCustomException(ContentErrorCode.NOT_FOUND_CONTENT) diff --git a/domain/src/main/kotlin/com/pokit/content/dto/request/ContentCommand.kt b/domain/src/main/kotlin/com/pokit/content/dto/request/ContentCommand.kt index 778aa03a..28f26dce 100644 --- a/domain/src/main/kotlin/com/pokit/content/dto/request/ContentCommand.kt +++ b/domain/src/main/kotlin/com/pokit/content/dto/request/ContentCommand.kt @@ -11,6 +11,11 @@ data class ContentCommand( val thumbNail: String? ) +data class CategorizeCommand( + val contentIds: List, + val categoryId: Long +) + fun ContentCommand.toDomain() = Content( categoryId = this.categoryId, data = this.data,