From 411e347972d9f33187b742b8d92c8f5f51894730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=9D=B8=EC=A4=80?= <54973090+dlswns2480@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:28:29 +0900 Subject: [PATCH] =?UTF-8?q?[feat=20#179]=20=EB=AF=B8=EB=B6=84=EB=A5=98=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EC=82=AD=EC=A0=9C(=EC=84=A0=ED=83=9D,=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4)=20API=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 미분류 링크 목록 삭제 API * feat : 미분류 링크 목록 삭제 로직 구현 * feat : 미분류 카테고리 조회, 링크 벌크 삭제 쿼리 구 * refactor : 주석, 로그 제거 --- .../com/pokit/content/ContentController.kt | 22 ++++++++++++++----- .../dto/request/DeleteUncategorizedRequest.kt | 9 ++++++++ .../category/impl/CategoryAdapter.kt | 5 +++++ .../category/persist/CategoryRepository.kt | 3 ++- .../content/impl/ContentAdapter.kt | 4 ++++ .../content/persist/ContentRepository.kt | 21 ++++++++++++++---- .../pokit/category/port/out/CategoryPort.kt | 1 + .../pokit/content/port/in/ContentUseCase.kt | 2 ++ .../com/pokit/content/port/out/ContentPort.kt | 3 ++- .../content/port/service/ContentService.kt | 17 ++++++++++++++ .../category/exception/CategoryErrorCode.kt | 2 ++ .../content/exception/ContentErrorCode.kt | 3 ++- 12 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 adapters/in-web/src/main/kotlin/com/pokit/content/dto/request/DeleteUncategorizedRequest.kt 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 765ba344..6e8deb5a 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 @@ -9,11 +9,11 @@ import com.pokit.common.dto.SliceResponseDto import com.pokit.common.wrapper.ResponseWrapper.wrapOk import com.pokit.common.wrapper.ResponseWrapper.wrapSlice import com.pokit.common.wrapper.ResponseWrapper.wrapUnit -import com.pokit.content.dto.request.ContentSearchParams -import com.pokit.content.dto.request.CreateContentRequest -import com.pokit.content.dto.request.UpdateContentRequest -import com.pokit.content.dto.request.toDto -import com.pokit.content.dto.response.* +import com.pokit.content.dto.request.* +import com.pokit.content.dto.response.BookMarkContentResponse +import com.pokit.content.dto.response.ContentResponse +import com.pokit.content.dto.response.ContentsResponse +import com.pokit.content.dto.response.toResponse import com.pokit.content.exception.ContentErrorCode import com.pokit.content.port.`in`.ContentUseCase import io.swagger.v3.oas.annotations.Operation @@ -169,5 +169,17 @@ class ContentController( .wrapSlice() .wrapOk() } + + @PutMapping("/uncategorized") + @Operation(summary = "미분류 링크 삭제 API") + @ErrorOperation(ContentErrorCode::class) + fun deleteUncategorizedContents( + @AuthenticationPrincipal user: PrincipalUser, + @RequestBody request: DeleteUncategorizedRequest + ): ResponseEntity { + return contentUseCase.deleteUncategorized(user.id, request.toDto()) + .wrapUnit() + } + } diff --git a/adapters/in-web/src/main/kotlin/com/pokit/content/dto/request/DeleteUncategorizedRequest.kt b/adapters/in-web/src/main/kotlin/com/pokit/content/dto/request/DeleteUncategorizedRequest.kt new file mode 100644 index 00000000..7b94ac55 --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/content/dto/request/DeleteUncategorizedRequest.kt @@ -0,0 +1,9 @@ +package com.pokit.content.dto.request + +data class DeleteUncategorizedRequest( + val contentId: List +) + +internal fun DeleteUncategorizedRequest.toDto() = this.contentId.map { it } + + diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryAdapter.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryAdapter.kt index 6f40e9bd..aaafab04 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryAdapter.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryAdapter.kt @@ -44,4 +44,9 @@ class CategoryAdapter( override fun loadByIdAndOpenType(id: Long, openType: OpenType): Category? = categoryRepository.findByIdAndOpenTypeAndDeleted(id, openType, false)?.toDomain() + override fun loadByNameAndUserId(categoryName: String, userId: Long): Category? { + return categoryRepository.findByNameAndUserId(categoryName, userId) + ?.toDomain() + } + } diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryRepository.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryRepository.kt index c542fa63..b5f7ecb4 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryRepository.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryRepository.kt @@ -10,5 +10,6 @@ interface CategoryRepository : JpaRepository { fun findByUserIdAndDeleted(userId: Long, deleted: Boolean, pageable: Pageable): Slice fun findByIdAndUserIdAndDeleted(id: Long, userId: Long, deleted: Boolean): CategoryEntity? fun countByUserIdAndDeleted(userId: Long, deleted: Boolean): Int - fun findByIdAndOpenTypeAndDeleted(id: Long, openType: OpenType, deleted:Boolean): CategoryEntity? + fun findByIdAndOpenTypeAndDeleted(id: Long, openType: OpenType, deleted: Boolean): CategoryEntity? + fun findByNameAndUserId(name: String, userId: Long): CategoryEntity? } 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 b4691db3..dd7c8601 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 @@ -200,6 +200,10 @@ class ContentAdapter( return contentRepository.findByIdInWithUser(contetIds) } + override fun deleteAllByIds(contentIds: List) { + contentRepository.deleteByContentIds(contentIds) + } + 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 4f67ead2..ecfccac9 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 @@ -23,11 +23,13 @@ interface ContentRepository : JpaRepository, ContentJdbcRep fun countByCategoryIdAndDeleted(id: Long, deleted: Boolean): Int @Modifying(clearAutomatically = true) - @Query(""" + @Query( + """ update ContentEntity c set c.deleted = true where c.categoryId in (select ct.id from CategoryEntity ct where ct.userId = :userId) - """) + """ + ) fun deleteByUserId(@Param("userId") userId: Long) fun findByIdIn(ids: List): List @@ -42,11 +44,22 @@ interface ContentRepository : JpaRepository, ContentJdbcRep ) fun countByUserId(userId: Long): Int - @Query(""" + @Query( + """ select new com.pokit.content.model.ContentWithUser(c.id, u.id, c.title, c.thumbNail) from ContentEntity c join CategoryEntity ca on ca.id = c.categoryId join UserEntity u on u.id = ca.userId where c.id in :ids - """) + """ + ) fun findByIdInWithUser(ids: List): List + + @Modifying(clearAutomatically = true) + @Query( + """ + update ContentEntity c set c.deleted = true + where c.id in :contentIds + """ + ) + fun deleteByContentIds(@Param("contentIds") contentIds: List) } diff --git a/application/src/main/kotlin/com/pokit/category/port/out/CategoryPort.kt b/application/src/main/kotlin/com/pokit/category/port/out/CategoryPort.kt index de76abb5..c0f30520 100644 --- a/application/src/main/kotlin/com/pokit/category/port/out/CategoryPort.kt +++ b/application/src/main/kotlin/com/pokit/category/port/out/CategoryPort.kt @@ -14,4 +14,5 @@ interface CategoryPort { fun delete(category: Category) fun countByUserId(userId: Long): Int fun loadByIdAndOpenType(id: Long, openType: OpenType): Category? + fun loadByNameAndUserId(categoryName: String, userId: Long): Category? } 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 f0c9f864..f6b98baf 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 @@ -40,4 +40,6 @@ interface ContentUseCase { fun getUnreadCount(userId: Long): Int fun getBookmarkCount(userId: Long): Int + + fun deleteUncategorized(userId: Long, contentIds: List) } 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 38a3d93d..ce4d9c5b 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 @@ -1,8 +1,8 @@ package com.pokit.content.port.out import com.pokit.category.model.OpenType -import com.pokit.content.dto.response.ContentsResult import com.pokit.content.dto.request.ContentSearchCondition +import com.pokit.content.dto.response.ContentsResult import com.pokit.content.dto.response.SharedContentResult import com.pokit.content.model.Content import com.pokit.content.model.ContentWithUser @@ -47,4 +47,5 @@ interface ContentPort { fun duplicateContent(originCategoryId: Long, targetCategoryId: Long) fun loadByContentIdsWithUser(contetIds: List): List + fun deleteAllByIds(contentIds: List) } 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 af6088f7..473b568c 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 @@ -6,10 +6,12 @@ import com.pokit.bookmark.model.Bookmark import com.pokit.bookmark.port.out.BookmarkPort import com.pokit.category.exception.CategoryErrorCode import com.pokit.category.model.Category +import com.pokit.category.model.CategoryStatus.UNCATEGORIZED import com.pokit.category.model.OpenType import com.pokit.category.port.out.CategoryPort 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.ContentCommand import com.pokit.content.dto.request.ContentSearchCondition @@ -164,6 +166,21 @@ class ContentService( override fun getBookmarkCount(userId: Long) = contentCountPort.getBookmarkCount(userId) + @Transactional + override fun deleteUncategorized(userId: Long, contentIds: List) { + val category = (categoryPort.loadByNameAndUserId(UNCATEGORIZED.displayName, userId) + ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_UNCATEGORIZED)) + + val contents = contentPort.loadByContentIds(contentIds) + contents.forEach { + if (it.categoryId != category.categoryId) { + throw ClientValidationException(ContentErrorCode.NOT_UNCATEGORIZED_CONTENT) + } + } + + contentPort.deleteAllByIds(contentIds) + } + 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/category/exception/CategoryErrorCode.kt b/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt index fad7ef90..9b0d9f1e 100644 --- a/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt +++ b/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt @@ -13,4 +13,6 @@ enum class CategoryErrorCode( MAX_CATEGORY_LIMIT_EXCEEDED("최대 30개의 포킷을 생성할 수 있습니다. 포킷을 삭제한 뒤에 추가해주세요.", "CA_005"), NOT_FOUND_UNCATEGORIZED_IMAGE("미분류 카테고리 이미지를 찾는데 실패했습니다.", "CA_006"), SHARE_ALREADY_EXISTS_CATEGORY("직접 생성한 포킷은 공유받을 수 없습니다.\n 다른 유저의 포킷을 공유받아보세요.", "CA_007"), + NOT_FOUND_UNCATEGORIZED("사용자가 미분류 카테고리가 없습니다.(서버 에러)", "CA_008"), + } diff --git a/domain/src/main/kotlin/com/pokit/content/exception/ContentErrorCode.kt b/domain/src/main/kotlin/com/pokit/content/exception/ContentErrorCode.kt index a7e7a1ef..c3f66fb1 100644 --- a/domain/src/main/kotlin/com/pokit/content/exception/ContentErrorCode.kt +++ b/domain/src/main/kotlin/com/pokit/content/exception/ContentErrorCode.kt @@ -6,5 +6,6 @@ enum class ContentErrorCode( override val message: String, override val code: String ) : ErrorCode { - NOT_FOUND_CONTENT("존재하지 않는 컨텐츠입니다.", "C_001") + NOT_FOUND_CONTENT("존재하지 않는 컨텐츠입니다.", "C_001"), + NOT_UNCATEGORIZED_CONTENT("해당 링크는 미분류 카테고리에 있지 않습니다.", "C_002"), }