From 60ef5e0f7bd83d14781d5acf29aab048bed8c3ed 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: Wed, 18 Dec 2024 18:57:54 +0900 Subject: [PATCH] =?UTF-8?q?[feat=20#204]=20=ED=8F=AC=ED=82=B7=20=EB=82=98?= =?UTF-8?q?=EA=B0=80=EA=B8=B0=20API=20(#205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 카테고리 owner_id 필드 추가 * feat : 포킷 나가기 api * feat : 포킷 나가기 로직 구현 - 포킷 생성 시 공유카테고리 생성 로직 추가 - 내보내기 시 방장인 지 검증 로직 추가 * feat : 방장 후보 조회 쿼리 구현 * feat : 카테고리 생성자 오류 수정 * feat : 공유카테고리 조회 시 deleted 조건 추가 --- .../category/v1/CategoryShareController.kt | 12 ++++- .../category/impl/SharedCategoryAdapter.kt | 7 +++ .../category/persist/CategoryEntity.kt | 9 +++- .../persist/SharedCategoryRepository.kt | 5 ++ .../pokit/category/port/in/CategoryUseCase.kt | 1 + .../category/port/out/SharedCategoryPort.kt | 2 + .../category/port/service/CategoryService.kt | 47 +++++++++++++++++-- .../pokit/user/port/service/UserService.kt | 3 +- .../category/exception/CategoryErrorCode.kt | 2 + .../com/pokit/category/model/Category.kt | 5 ++ 10 files changed, 84 insertions(+), 9 deletions(-) diff --git a/adapters/in-web/src/main/kotlin/com/pokit/category/v1/CategoryShareController.kt b/adapters/in-web/src/main/kotlin/com/pokit/category/v1/CategoryShareController.kt index 7471be03..8b676145 100644 --- a/adapters/in-web/src/main/kotlin/com/pokit/category/v1/CategoryShareController.kt +++ b/adapters/in-web/src/main/kotlin/com/pokit/category/v1/CategoryShareController.kt @@ -2,9 +2,9 @@ package com.pokit.category.v1 import com.pokit.auth.aop.KakaoAuth import com.pokit.auth.model.PrincipalUser +import com.pokit.category.port.`in`.CategoryUseCase import com.pokit.category.v1.dto.request.DuplicateCategoryRequest import com.pokit.category.v1.dto.response.SharedContentsResponse -import com.pokit.category.port.`in`.CategoryUseCase import com.pokit.common.wrapper.ResponseWrapper.wrapOk import com.pokit.common.wrapper.ResponseWrapper.wrapUnit import com.pokit.content.port.`in`.ContentUseCase @@ -86,4 +86,14 @@ class CategoryShareController( .wrapUnit() } + @Operation(summary = "포킷 나가기 API") + @PostMapping("/out/{categoryId}") + fun outCategory( + @AuthenticationPrincipal user: PrincipalUser, + @PathVariable categoryId: Long, + ): ResponseEntity { + return categoryUseCase.outCategory(user.id, categoryId) + .wrapUnit() + } + } diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/SharedCategoryAdapter.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/SharedCategoryAdapter.kt index 29884166..1a450757 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/SharedCategoryAdapter.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/SharedCategoryAdapter.kt @@ -29,4 +29,11 @@ class SharedCategoryAdapter( sharedCategoryRepository.findByIdOrNull(sharedCategory.id) ?.delete() } + + override fun loadFirstByCategoryId(categoryId: Long): SharedCategory? { + return sharedCategoryRepository.findFirstByCategoryIdAndIsDeletedOrderByCreatedAt( + categoryId, + false + )?.toDomain() + } } diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryEntity.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryEntity.kt index 4c41b779..0acc0583 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryEntity.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryEntity.kt @@ -38,6 +38,9 @@ class CategoryEntity( @Column(name = "keyword") @Enumerated(EnumType.STRING) var keyword: InterestType = InterestType.DEFAULT, + + @Column(name = "owner_id") + var ownerId: Long, ) : BaseEntity() { @Column(name = "is_deleted") @@ -57,7 +60,8 @@ class CategoryEntity( openType = category.openType, userCount = category.userCount, isShared = category.isShared, - keyword = category.keyword + keyword = category.keyword, + ownerId = category.ownerId, ) } } @@ -71,5 +75,6 @@ fun CategoryEntity.toDomain() = Category( openType = this.openType, userCount = this.userCount, isShared = this.isShared, - keyword = this.keyword + keyword = this.keyword, + ownerId = this.ownerId, ) diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/SharedCategoryRepository.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/SharedCategoryRepository.kt index a35329af..ffda33cc 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/SharedCategoryRepository.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/SharedCategoryRepository.kt @@ -8,4 +8,9 @@ interface SharedCategoryRepository : JpaRepository { categoryId: Long, isDeleted: Boolean ): SharedCategoryEntity? + + fun findFirstByCategoryIdAndIsDeletedOrderByCreatedAt( + categoryId: Long, + deleted: Boolean + ): SharedCategoryEntity? } 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 52bd682a..c17ba025 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 @@ -20,4 +20,5 @@ interface CategoryUseCase { fun duplicateCategory(originCategoryId: Long, categoryName: String, userId: Long, categoryImageId: Int) fun acceptCategory(userId: Long, categoryId: Long) fun resignUser(userId: Long, categoryId: Long, resignUserId: Long) + fun outCategory(userId: Long, categoryId: Long) } diff --git a/application/src/main/kotlin/com/pokit/category/port/out/SharedCategoryPort.kt b/application/src/main/kotlin/com/pokit/category/port/out/SharedCategoryPort.kt index 5d9c37e7..ff0145f5 100644 --- a/application/src/main/kotlin/com/pokit/category/port/out/SharedCategoryPort.kt +++ b/application/src/main/kotlin/com/pokit/category/port/out/SharedCategoryPort.kt @@ -8,4 +8,6 @@ interface SharedCategoryPort { fun loadByUserIdAndCategoryId(userId: Long, categoryId: Long): SharedCategory? fun delete(sharedCategory: SharedCategory) + + fun loadFirstByCategoryId(categoryId: Long): SharedCategory? } 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 633c572d..a9fcdc1b 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 @@ -50,15 +50,25 @@ class CategoryService( val categoryImage = categoryImagePort.loadById(command.categoryImageId) ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY_IMAGE) - return categoryPort.persist( + val category = categoryPort.persist( Category( categoryName = command.categoryName, categoryImage = categoryImage, userId = userId, openType = command.openType, - keyword = command.keywordType + keyword = command.keywordType, + ownerId = userId ) ) + + val sharedCategory = SharedCategory( + userId = userId, + categoryId = category.categoryId + ) + + sharedCategoryPort.persist(sharedCategory) + + return category } @Transactional @@ -167,17 +177,44 @@ class CategoryService( categoryPort.persist(category) - val sharedCategory = SharedCategory(userId = userId, categoryId = category.categoryId) + val sharedCategory = SharedCategory( + userId = userId, + categoryId = category.categoryId, + ) sharedCategoryPort.persist(sharedCategory) } @Transactional override fun resignUser(userId: Long, categoryId: Long, resignUserId: Long) { - val category = categoryPort.loadByIdAndUserId(categoryId, userId) - ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY) + val category = categoryPort.loadCategoryOrThrow(categoryId, userId) + if (category.userId != userId) { + throw ClientValidationException(CategoryErrorCode.NOT_OWNER) + } val sharedCategory = (sharedCategoryPort.loadByUserIdAndCategoryId(resignUserId, category.categoryId) ?: throw NotFoundCustomException(CategoryErrorCode.NEVER_ACCPTED)) + sharedCategoryPort.delete(sharedCategory) + + category.minusUserCount() // 포킷 인원수 감소 + categoryPort.persist(category) + } + + @Transactional + override fun outCategory(userId: Long, categoryId: Long) { + val category = categoryPort.loadByIdOrThrow(categoryId) + + val sharedCategory = sharedCategoryPort.loadByUserIdAndCategoryId(userId, categoryId) + ?: throw NotFoundCustomException(CategoryErrorCode.NEVER_ACCPTED) + sharedCategoryPort.delete(sharedCategory) + + if (category.ownerId == userId) { // 나간사람이 방장이었다면 + val firstSharedCategory = sharedCategoryPort.loadFirstByCategoryId(categoryId) + ?: throw ClientValidationException(CategoryErrorCode.EMPTY_USER_IN_CATEGORY) + category.ownerId = firstSharedCategory.userId // 권한 위임 + } + + category.minusUserCount() // 포킷 인원수 감소 + categoryPort.persist(category) } override fun getAllCategoryImages(): List = diff --git a/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt b/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt index b7e71324..7f445c4f 100644 --- a/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt +++ b/application/src/main/kotlin/com/pokit/user/port/service/UserService.kt @@ -59,7 +59,8 @@ class UserService( categoryName = UNCATEGORIZED.displayName, categoryImage = image, openType = OpenType.PRIVATE, - keyword = InterestType.DEFAULT + keyword = InterestType.DEFAULT, + ownerId = user.id, ) categoryPort.persist(category) 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 e3e58e3a..f18c3431 100644 --- a/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt +++ b/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt @@ -17,5 +17,7 @@ enum class CategoryErrorCode( ALREADY_ACCEPTED("이미 초대를 수락한 포킷입니다.", "CA_009"), NEVER_ACCPTED("해당 유저가 포킷에 초대된 이력이 없습니다.", "CA_0010"), INVALID_OPENTYPE("OpenType은 PUBLIC, PRIVATE중 하나여야 합니다.", "CA_0011"), + EMPTY_USER_IN_CATEGORY("포킷에 방장 제외 아무도 없습니다.", "CA_0012"), + NOT_OWNER("해당 유저는 방장이 아닙니다.", "CA_0013"), } 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 49fbd7fd..5ffce791 100644 --- a/domain/src/main/kotlin/com/pokit/category/model/Category.kt +++ b/domain/src/main/kotlin/com/pokit/category/model/Category.kt @@ -15,6 +15,7 @@ data class Category( var userCount: Int = 0, var isShared: Boolean = false, var keyword: InterestType, + var ownerId: Long, ) { fun update(command: CategoryCommand, categoryImage: CategoryImage) { this.categoryName = command.categoryName @@ -32,6 +33,10 @@ data class Category( this.userCount++ } + fun minusUserCount() { + this.userCount-- + } + fun shared() { this.isShared = true }