diff --git a/adapters/in-web/src/main/kotlin/com/pokit/category/CategoryController.kt b/adapters/in-web/src/main/kotlin/com/pokit/category/CategoryController.kt new file mode 100644 index 00000000..3b95cf78 --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/category/CategoryController.kt @@ -0,0 +1,97 @@ +package com.pokit.category + +import com.pokit.auth.config.ErrorOperation +import com.pokit.auth.model.PrincipalUser +import com.pokit.category.dto.request.CreateCategoryRequest +import com.pokit.category.dto.request.toDto +import com.pokit.category.dto.response.CategoryCountResponse +import com.pokit.category.dto.response.CategoryResponse +import com.pokit.category.dto.response.toResponse +import com.pokit.category.exception.CategoryErrorCode +import com.pokit.category.model.Category +import com.pokit.category.model.CategoryImage +import com.pokit.category.port.`in`.CategoryUseCase +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 io.swagger.v3.oas.annotations.Operation +import jakarta.validation.Valid +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Sort +import org.springframework.data.web.PageableDefault +import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/api/v1/category") +class CategoryController( + private val categoryUseCase: CategoryUseCase +) { + @Operation(summary = "포킷 생성 API") + @ErrorOperation(CategoryErrorCode::class) + @PostMapping + fun createCategory( + @AuthenticationPrincipal user: PrincipalUser, + @Valid @RequestBody request: CreateCategoryRequest + ): ResponseEntity = + categoryUseCase.create(request.toDto(), user.id) + .toResponse() + .wrapOk() + + @Operation(summary = "포킷 목록 조회 API") + @GetMapping + fun getCategories( + @AuthenticationPrincipal user: PrincipalUser, + @PageableDefault( + page = 0, + size = 10, + sort = ["createdAt"], + direction = Sort.Direction.DESC + ) pageable: Pageable, + @RequestParam(defaultValue = "true") filterUncategorized: Boolean + ): ResponseEntity> = + categoryUseCase.getCategories(user.id, pageable, filterUncategorized) + .wrapSlice() + .wrapOk() + + @Operation(summary = "포킷 수정 API") + @ErrorOperation(CategoryErrorCode::class) + @PatchMapping("/{categoryId}") + fun updateCategory( + @AuthenticationPrincipal user: PrincipalUser, + @Valid @RequestBody request: CreateCategoryRequest, + @PathVariable categoryId: Long, + ): ResponseEntity = + categoryUseCase.update(request.toDto(), user.id, categoryId) + .toResponse() + .wrapOk() + + @Operation(summary = "포킷 삭제 API") + @ErrorOperation(CategoryErrorCode::class) + @PutMapping("/{categoryId}") + fun deleteCategory( + @AuthenticationPrincipal user: PrincipalUser, + @PathVariable categoryId: Long, + ): ResponseEntity = + categoryUseCase.delete(categoryId, user.id) + .wrapUnit() + + @Operation(summary = "유저의 포킷 개수 조회 API") + @GetMapping("/count") + fun getTotalCount( + @AuthenticationPrincipal user: PrincipalUser, + ): ResponseEntity { + val count = categoryUseCase.getTotalCount(user.id) + return CategoryCountResponse(count) + .wrapOk() + } + + @Operation(summary = "포킷 프로필 목록 조회 API") + @GetMapping("/images") + fun getCategoryImages(): ResponseEntity> = + categoryUseCase.getAllCategoryImages() + .wrapOk() + +} diff --git a/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/CreateCategoryRequest.kt b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/CreateCategoryRequest.kt new file mode 100644 index 00000000..e28450bb --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/CreateCategoryRequest.kt @@ -0,0 +1,17 @@ +package com.pokit.category.dto.request + +import com.pokit.category.dto.CategoryCommand +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + +data class CreateCategoryRequest( + @field:NotBlank(message = "포킷 명은 필수 값입니다.") + @field:Size(min = 1, max = 10, message = "최대 10자까지 입력 가능합니다.") + val categoryName: String, + val categoryImageId: Int, +) + +internal fun CreateCategoryRequest.toDto() = CategoryCommand( + categoryName = this.categoryName, + categoryImageId = this.categoryImageId +) diff --git a/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/UpdateCategoryRequest.kt b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/UpdateCategoryRequest.kt new file mode 100644 index 00000000..53a63f39 --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/request/UpdateCategoryRequest.kt @@ -0,0 +1,17 @@ +package com.pokit.category.dto.request + +import com.pokit.category.dto.CategoryCommand +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size + +data class UpdateCategoryRequest ( + @field:NotBlank(message = "포킷 명은 필수 값입니다.") + @field:Size(min = 1, max = 10, message = "최대 10자까지 입력 가능합니다.") + val categoryName: String, + val categoryImageId: Int, +) + +internal fun UpdateCategoryRequest.toDto() = CategoryCommand( + categoryName = this.categoryName, + categoryImageId = this.categoryImageId +) diff --git a/adapters/in-web/src/main/kotlin/com/pokit/category/dto/response/CategoryResponse.kt b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/response/CategoryResponse.kt new file mode 100644 index 00000000..48829e66 --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/category/dto/response/CategoryResponse.kt @@ -0,0 +1,20 @@ +package com.pokit.category.dto.response + +import com.pokit.category.model.Category +import com.pokit.category.model.CategoryImage + +data class CategoryResponse( + val categoryId: Long, + var categoryName: String, + var categoryImage: CategoryImage, +) + +data class CategoryCountResponse( + val categoryTotalCount: Int, +) + +fun Category.toResponse(): CategoryResponse = CategoryResponse( + categoryId = this.categoryId, + categoryName = this.categoryName, + categoryImage = this.categoryImage, +) diff --git a/adapters/in-web/src/main/kotlin/com/pokit/common/dto/SliceResponseDto.kt b/adapters/in-web/src/main/kotlin/com/pokit/common/dto/SliceResponseDto.kt new file mode 100644 index 00000000..01e0e94d --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/common/dto/SliceResponseDto.kt @@ -0,0 +1,20 @@ +package com.pokit.common.dto + +import org.springframework.data.domain.Slice +import org.springframework.data.domain.Sort + +data class SliceResponseDto( + val data: List, + val page: Int?, + val size: Int?, + val sort: Sort, + val hasNext: Boolean, +) { + constructor(slice: Slice) : this( + data = slice.content, + page = slice.pageable.pageNumber, + size = slice.size, + sort = slice.sort, + hasNext = slice.hasNext() + ) +} diff --git a/adapters/in-web/src/main/kotlin/com/pokit/common/exception/ApiExceptionHandler.kt b/adapters/in-web/src/main/kotlin/com/pokit/common/exception/ApiExceptionHandler.kt index 38e60ed8..1976953c 100644 --- a/adapters/in-web/src/main/kotlin/com/pokit/common/exception/ApiExceptionHandler.kt +++ b/adapters/in-web/src/main/kotlin/com/pokit/common/exception/ApiExceptionHandler.kt @@ -1,5 +1,6 @@ package com.pokit.common.exception +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.http.HttpStatus import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler @@ -8,6 +9,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice @RestControllerAdvice class ApiExceptionHandler { + private val logger = KotlinLogging.logger { } + private val notValidMessage = "잘못된 입력 값입니다." private val notValidCode = "G_001" @@ -16,7 +19,7 @@ class ApiExceptionHandler { fun handleMethodArgumentNotValidationException(e: MethodArgumentNotValidException): ErrorResponse { var message = notValidMessage val allErrors = e.bindingResult.allErrors - if (!allErrors.isEmpty()) { + if (allErrors.isNotEmpty()) { message = allErrors[0].defaultMessage!! } @@ -24,10 +27,9 @@ class ApiExceptionHandler { } @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(NotFoundCustomException::class) - fun handleNotFoundException(e: NotFoundCustomException) = ErrorResponse(e.message!!, e.code) - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(ClientValidationException::class) - fun handleClientValidationException(e: ClientValidationException) = ErrorResponse(e.message!!, e.code) + @ExceptionHandler(PokitException::class) + fun handlePokitException(e: PokitException): ErrorResponse { + logger.warn { "PokitException: ${e.message} / $e" } + return ErrorResponse(e.errorCode.message, e.errorCode.code) + } } diff --git a/adapters/in-web/src/main/kotlin/com/pokit/common/wrapper/ResponseWrapper.kt b/adapters/in-web/src/main/kotlin/com/pokit/common/wrapper/ResponseWrapper.kt new file mode 100644 index 00000000..7a8d6fbb --- /dev/null +++ b/adapters/in-web/src/main/kotlin/com/pokit/common/wrapper/ResponseWrapper.kt @@ -0,0 +1,13 @@ +package com.pokit.common.wrapper + +import com.pokit.common.dto.SliceResponseDto +import org.springframework.data.domain.Slice +import org.springframework.http.ResponseEntity + +object ResponseWrapper { + fun T.wrapOk(): ResponseEntity = ResponseEntity.ok(this) + + fun Slice.wrapSlice() = SliceResponseDto(this) + + fun Unit.wrapUnit(): ResponseEntity = ResponseEntity.noContent().build() +} 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 new file mode 100644 index 00000000..e6cf1dbb --- /dev/null +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryAdapter.kt @@ -0,0 +1,39 @@ +package com.pokit.out.persistence.category.impl + +import com.pokit.category.model.Category +import com.pokit.category.port.out.CategoryPort +import com.pokit.out.persistence.category.persist.CategoryEntity +import com.pokit.out.persistence.category.persist.CategoryRepository +import com.pokit.out.persistence.category.persist.toDomain +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Slice +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Component + +@Component +class CategoryAdapter( + private val categoryRepository: CategoryRepository +) : CategoryPort { + override fun loadAllByUserId(userId: Long, pageable: Pageable): Slice = + categoryRepository.findByUserIdAndDeleted(userId, false, pageable) + .map { it.toDomain() } + + override fun loadByIdAndUserId(id: Long, userId: Long): Category? = + categoryRepository.findByIdAndUserIdAndDeleted(id, userId, false)?.toDomain() + + override fun existsByNameAndUserId(name: String, userId: Long): Boolean = + categoryRepository.existsByNameAndUserIdAndDeleted(name, userId, false) + + override fun persist(category: Category): Category { + val categoryEntity = CategoryEntity.of(category) + return categoryRepository.save(categoryEntity).toDomain() + } + + override fun delete(category: Category) { + categoryRepository.findByIdOrNull(category.categoryId) + ?.delete() + } + + override fun countByUserId(userId: Long): Int = + categoryRepository.countByUserIdAndDeleted(userId, false) +} diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryImageAdapter.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryImageAdapter.kt new file mode 100644 index 00000000..f7e4b362 --- /dev/null +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/impl/CategoryImageAdapter.kt @@ -0,0 +1,20 @@ +package com.pokit.out.persistence.category.impl + +import com.pokit.category.model.CategoryImage +import com.pokit.category.port.out.CategoryImagePort +import com.pokit.out.persistence.category.persist.CategoryImageRepository +import com.pokit.out.persistence.category.persist.toDomain +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Component + +@Component +class CategoryImageAdapter( + private val categoryImageRepository: CategoryImageRepository +) : CategoryImagePort { + override fun loadById(id: Int): CategoryImage? = + categoryImageRepository.findByIdOrNull(id)?.toDomain() + + override fun loadAll(): List = + categoryImageRepository.findAll() + .map { it.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 4b529d77..7ebd5473 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 @@ -1,6 +1,6 @@ package com.pokit.out.persistence.category.persist -import com.pokit.category.model.OpenType +import com.pokit.category.model.Category import com.pokit.out.persistence.BaseEntity import jakarta.persistence.* @@ -19,14 +19,32 @@ class CategoryEntity( @Column(name = "name") var name: String, - @Column(name = "memo") - var memo: String, - - @Enumerated(EnumType.STRING) - @Column(name = "open_type") - val openType: OpenType, - @OneToOne @JoinColumn(name = "image_id") val image: CategoryImageEntity, -) : BaseEntity() +) : BaseEntity() { + + @Column(name = "id_deleted") + var deleted: Boolean = false + + fun delete() { + this.deleted = true + } + + companion object { + fun of(category: Category) = + CategoryEntity( + id = category.categoryId, + userId = category.userId, + name = category.categoryName, + image = CategoryImageEntity.of(category.categoryImage) + ) + } +} + +fun CategoryEntity.toDomain() = Category( + categoryId = this.id, + categoryName = this.name, + categoryImage = this.image.toDomain(), + userId = this.userId, +) diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryImageEntity.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryImageEntity.kt index 63ed4f50..ea45348f 100644 --- a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryImageEntity.kt +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryImageEntity.kt @@ -1,5 +1,7 @@ package com.pokit.out.persistence.category.persist +import com.pokit.category.model.Category +import com.pokit.category.model.CategoryImage import jakarta.persistence.* @Table(name = "CATEGORY_IMAGE") @@ -8,8 +10,22 @@ class CategoryImageEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") - val id: Long = 0L, + val id: Int = 0, - @Column(name = "path") - val path: String, + @Column(name = "url") + val url: String, +) { + companion object { + fun of(categoryImage: CategoryImage) = + CategoryImageEntity( + id = categoryImage.imageId, + url = categoryImage.imageUrl + ) + } +} + +fun CategoryImageEntity.toDomain() = CategoryImage( + imageId = this.id, + imageUrl = this.url ) + diff --git a/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryImageRepository.kt b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryImageRepository.kt new file mode 100644 index 00000000..c13f7e6f --- /dev/null +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryImageRepository.kt @@ -0,0 +1,6 @@ +package com.pokit.out.persistence.category.persist + +import org.springframework.data.jpa.repository.JpaRepository + +interface CategoryImageRepository : JpaRepository { +} 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 new file mode 100644 index 00000000..52fd5d4e --- /dev/null +++ b/adapters/out-persistence/src/main/kotlin/com/pokit/out/persistence/category/persist/CategoryRepository.kt @@ -0,0 +1,12 @@ +package com.pokit.out.persistence.category.persist + +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Slice +import org.springframework.data.jpa.repository.JpaRepository + +interface CategoryRepository : JpaRepository { + fun existsByNameAndUserIdAndDeleted(name: String, userId: Long, deleted: Boolean): Boolean + 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 +} diff --git a/application/build.gradle.kts b/application/build.gradle.kts index f8f0edd2..41ceb6b9 100644 --- a/application/build.gradle.kts +++ b/application/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { // 라이브러리 implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.data:spring-data-commons") implementation("org.springframework:spring-tx") implementation("io.jsonwebtoken:jjwt-api:0.12.5") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5") 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 new file mode 100644 index 00000000..a642a002 --- /dev/null +++ b/application/src/main/kotlin/com/pokit/category/port/in/CategoryUseCase.kt @@ -0,0 +1,16 @@ +package com.pokit.category.port.`in` + +import com.pokit.category.dto.CategoryCommand +import com.pokit.category.model.Category +import com.pokit.category.model.CategoryImage +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Slice + +interface CategoryUseCase { + fun create(categoryCommand: CategoryCommand, userId: Long): Category + fun update(categoryCommand: CategoryCommand, userId: Long, categoryId: Long): Category + fun delete(categoryId: Long, userId: Long) + fun getTotalCount(userId: Long): Int + fun getAllCategoryImages(): List + fun getCategories(userId: Long, pageable: Pageable, filterUncategorized: Boolean): Slice +} diff --git a/application/src/main/kotlin/com/pokit/category/port/out/CategoryImagePort.kt b/application/src/main/kotlin/com/pokit/category/port/out/CategoryImagePort.kt new file mode 100644 index 00000000..439181dc --- /dev/null +++ b/application/src/main/kotlin/com/pokit/category/port/out/CategoryImagePort.kt @@ -0,0 +1,8 @@ +package com.pokit.category.port.out + +import com.pokit.category.model.CategoryImage + +interface CategoryImagePort { + fun loadById(id: Int): CategoryImage? + fun loadAll(): 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 new file mode 100644 index 00000000..a47fb7ae --- /dev/null +++ b/application/src/main/kotlin/com/pokit/category/port/out/CategoryPort.kt @@ -0,0 +1,14 @@ +package com.pokit.category.port.out + +import com.pokit.category.model.Category +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Slice + +interface CategoryPort { + fun loadAllByUserId(userId: Long, pageable: Pageable): Slice + fun loadByIdAndUserId(id: Long, userId: Long): Category? + fun existsByNameAndUserId(name: String, userId: Long): Boolean + fun persist(category: Category): Category + fun delete(category: Category) + fun countByUserId(userId: Long): Int +} 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 new file mode 100644 index 00000000..c5517a2d --- /dev/null +++ b/application/src/main/kotlin/com/pokit/category/port/service/CategoryService.kt @@ -0,0 +1,95 @@ +package com.pokit.category.port.service + +import com.pokit.category.dto.CategoryCommand +import com.pokit.category.exception.CategoryErrorCode +import com.pokit.category.model.Category +import com.pokit.category.model.CategoryImage +import com.pokit.category.model.CategoryStatus.UNCATEGORIZED +import com.pokit.category.port.`in`.CategoryUseCase +import com.pokit.category.port.out.CategoryImagePort +import com.pokit.category.port.out.CategoryPort +import com.pokit.common.exception.AlreadyExistsException +import com.pokit.common.exception.InvalidRequestException +import com.pokit.common.exception.NotFoundCustomException +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Slice +import org.springframework.data.domain.SliceImpl +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +@Transactional(readOnly = true) +class CategoryService( + private val categoryPort: CategoryPort, + private val categoryImagePort: CategoryImagePort +) : CategoryUseCase { + companion object { + private const val MAX_CATEGORY_COUNT = 30 + } + + @Transactional + override fun create(command: CategoryCommand, userId: Long): Category { + if (command.categoryName == UNCATEGORIZED.displayName) { + throw InvalidRequestException(CategoryErrorCode.UNAVAILABLE_CATEGORY_NAME) + } + + if (categoryPort.existsByNameAndUserId(command.categoryName, userId)) { + throw AlreadyExistsException(CategoryErrorCode.ALREADY_EXISTS_CATEGORY) + } + + if (categoryPort.countByUserId(userId) >= MAX_CATEGORY_COUNT) { + throw InvalidRequestException(CategoryErrorCode.MAX_CATEGORY_LIMIT_EXCEEDED) + } + + val categoryImage = categoryImagePort.loadById(command.categoryImageId) + ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY_IMAGE) + + return categoryPort.persist( + Category( + categoryName = command.categoryName, + categoryImage = categoryImage, + userId = userId, + ) + ) + } + + @Transactional + override fun update(categoryCommand: CategoryCommand, userId: Long, categoryId: Long): Category { + val category = categoryPort.loadByIdAndUserId(categoryId, userId) + ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY) + + val categoryImage = categoryImagePort.loadById(categoryCommand.categoryImageId) + ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY_IMAGE) + + category.update(categoryCommand.categoryName, categoryImage) + return categoryPort.persist(category) + } + + @Transactional + override fun delete(categoryId: Long, userId: Long) { + val category = categoryPort.loadByIdAndUserId(categoryId, userId) + ?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY) + + categoryPort.delete(category) + } + + override fun getTotalCount(userId: Long): Int = + categoryPort.countByUserId(userId) + + override fun getCategories(userId: Long, pageable: Pageable, filterUncategorized: Boolean): Slice { + val categories = categoryPort.loadAllByUserId(userId, pageable) + + val filteredCategories = if (filterUncategorized) { + categories.content.filter { it.categoryName != UNCATEGORIZED.displayName } + } else { + categories.content + } + + return SliceImpl(filteredCategories, pageable, categories.hasNext()) + } + + + override fun getAllCategoryImages(): List = + categoryImagePort.loadAll() + +} diff --git a/build.gradle.kts b/build.gradle.kts index a6dd27c7..61eb78db 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,9 @@ subprojects { // kotest testImplementation("io.kotest:kotest-runner-junit5-jvm:5.8.1") testImplementation("io.kotest:kotest-assertions-core-jvm:5.8.1") + + // logging + implementation("io.github.oshai:kotlin-logging-jvm:7.0.0") } tasks.test { diff --git a/domain/src/main/kotlin/com/pokit/category/dto/CategoryCommand.kt b/domain/src/main/kotlin/com/pokit/category/dto/CategoryCommand.kt new file mode 100644 index 00000000..b517f21f --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/category/dto/CategoryCommand.kt @@ -0,0 +1,6 @@ +package com.pokit.category.dto + +data class CategoryCommand ( + val categoryName: String, + val categoryImageId: Int, +) diff --git a/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt b/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt new file mode 100644 index 00000000..eb1ab8e8 --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/category/exception/CategoryErrorCode.kt @@ -0,0 +1,14 @@ +package com.pokit.category.exception + +import com.pokit.common.exception.ErrorCode + +enum class CategoryErrorCode ( + override val message: String, + override val code: String, +): ErrorCode { + ALREADY_EXISTS_CATEGORY("사용 중인 포킷명입니다.", "C_001"), + NOT_FOUND_CATEGORY("포킷 정보를 찾을 수 없습니다.", "C_002"), + UNAVAILABLE_CATEGORY_NAME("사용할 수 없는 포킷명입니다.", "C_003"), + NOT_FOUND_CATEGORY_IMAGE("포킷 이미지 정보를 찾을 수 없습니다.", "C_004"), + MAX_CATEGORY_LIMIT_EXCEEDED("최대 30개의 포킷을 생성할 수 있습니다. 포킷을 삭제한 뒤에 추가해주세요.", "C_005") +} diff --git a/domain/src/main/kotlin/com/pokit/category/model/Category.kt b/domain/src/main/kotlin/com/pokit/category/model/Category.kt new file mode 100644 index 00000000..13512c85 --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/category/model/Category.kt @@ -0,0 +1,13 @@ +package com.pokit.category.model + +data class Category( + val categoryId: Long = 0L, + val userId: Long, + var categoryName: String, + var categoryImage: CategoryImage, +) { + fun update(categoryName: String, categoryImage: CategoryImage) { + this.categoryName = categoryName + this.categoryImage = categoryImage + } +} diff --git a/domain/src/main/kotlin/com/pokit/category/model/CategoryImage.kt b/domain/src/main/kotlin/com/pokit/category/model/CategoryImage.kt new file mode 100644 index 00000000..9682cbe7 --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/category/model/CategoryImage.kt @@ -0,0 +1,6 @@ +package com.pokit.category.model + +data class CategoryImage( + var imageId: Int, + val imageUrl: String, +) diff --git a/domain/src/main/kotlin/com/pokit/category/model/CategoryStatus.kt b/domain/src/main/kotlin/com/pokit/category/model/CategoryStatus.kt new file mode 100644 index 00000000..49ffe854 --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/category/model/CategoryStatus.kt @@ -0,0 +1,5 @@ +package com.pokit.category.model + +enum class CategoryStatus(val displayName: String) { + UNCATEGORIZED("미분류") +} diff --git a/domain/src/main/kotlin/com/pokit/common/exception/ClientValidationException.kt b/domain/src/main/kotlin/com/pokit/common/exception/ClientValidationException.kt deleted file mode 100644 index fe2252c8..00000000 --- a/domain/src/main/kotlin/com/pokit/common/exception/ClientValidationException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.pokit.common.exception - -class ClientValidationException( - errorCode: ErrorCode, -) : RuntimeException(errorCode.message) { - val code = errorCode.code -} diff --git a/domain/src/main/kotlin/com/pokit/common/exception/NotFoundCustomException.kt b/domain/src/main/kotlin/com/pokit/common/exception/NotFoundCustomException.kt deleted file mode 100644 index bfe42248..00000000 --- a/domain/src/main/kotlin/com/pokit/common/exception/NotFoundCustomException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.pokit.common.exception - -class NotFoundCustomException( - errorCode: ErrorCode, -) : RuntimeException(errorCode.message) { - val code = errorCode.code -} diff --git a/domain/src/main/kotlin/com/pokit/common/exception/PokitException.kt b/domain/src/main/kotlin/com/pokit/common/exception/PokitException.kt new file mode 100644 index 00000000..22c2fc47 --- /dev/null +++ b/domain/src/main/kotlin/com/pokit/common/exception/PokitException.kt @@ -0,0 +1,13 @@ +package com.pokit.common.exception + +open class PokitException( + val errorCode: ErrorCode, +) : RuntimeException(errorCode.message) + +class ClientValidationException(errorCode: ErrorCode) : PokitException(errorCode) + +class InvalidRequestException(errorCode: ErrorCode) : PokitException(errorCode) + +class NotFoundCustomException(errorCode: ErrorCode) : PokitException(errorCode) + +class AlreadyExistsException(errorCode: ErrorCode) : PokitException(errorCode)