Skip to content

Commit

Permalink
[feat #106] 공유 컨텐츠 미리보기 API (#107)
Browse files Browse the repository at this point in the history
* feat: 공유 컨텐츠 미리보기 API

* fix: 타이틀 max 1000으로 조정

* fix: 오탈자 수정

* fix: datetime 포맷 수정

* fix: 필요없는 반환 삭제
  • Loading branch information
jimin3263 authored Aug 18, 2024
1 parent d67c2a3 commit 132626e
Show file tree
Hide file tree
Showing 21 changed files with 274 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.pokit.category

import com.pokit.auth.model.PrincipalUser
import com.pokit.category.dto.response.SharedContentsResponse
import com.pokit.category.port.`in`.CategoryUseCase
import com.pokit.common.wrapper.ResponseWrapper.wrapOk
import com.pokit.content.port.`in`.ContentUseCase
import io.swagger.v3.oas.annotations.Operation
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/share")
class CategoryShareController(
private val categoryUseCase: CategoryUseCase,
private val contentUseCase: ContentUseCase,
) {
@Operation(summary = "포킷 공유 후 callback API")
@PostMapping("/callback")
fun completeShare(
@AuthenticationPrincipal user: PrincipalUser,
@RequestParam("categoryId") categoryId: Long,
): ResponseEntity<Unit> {
return categoryUseCase.completeShare(categoryId, user.id)
.wrapOk()
}

@Operation(summary = "포킷 공유 시 포킷 내 컨텐츠 미리보기 API")
@GetMapping("/{categoryId}")
fun getSharedContents(
@AuthenticationPrincipal user: PrincipalUser,
@PathVariable categoryId: Long,
@PageableDefault(
page = 0,
size = 10,
sort = ["createdAt"],
direction = Sort.Direction.DESC
) pageable: Pageable,
): ResponseEntity<SharedContentsResponse> {
val category = categoryUseCase.getSharedCategory(categoryId, user.id)
val content = contentUseCase.getSharedContents(categoryId, pageable)
return SharedContentsResponse
.from(content, category)
.wrapOk()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.pokit.category.dto.response

import com.pokit.category.model.Category
import com.pokit.common.dto.DateFormatters
import com.pokit.common.dto.SliceResponseDto
import com.pokit.common.wrapper.ResponseWrapper.wrapSlice
import com.pokit.content.dto.response.SharedContentResult
import org.springframework.data.domain.Slice
import org.springframework.data.domain.SliceImpl

data class SharedContentsResponse(
var category: SharedCategoryResponse,
var contents: SliceResponseDto<SharedContentResponse>
) {
companion object {
fun from(sharedContents: Slice<SharedContentResult>, category: Category): SharedContentsResponse {
val sharedContentResponse = sharedContents.content.map { SharedContentResponse.of(it) }
val contents = SliceImpl(sharedContentResponse, sharedContents.pageable, sharedContents.hasNext())

return SharedContentsResponse(
category = SharedCategoryResponse.of(category),
contents = contents.wrapSlice(),
)
}
}
}

data class SharedContentResponse(
val contentId: Long,
val data: String,
val domain: String,
val title: String,
val memo: String,
val createdAt: String,
val thumbNail: String,
) {
companion object {
fun of(content: SharedContentResult): SharedContentResponse {
return SharedContentResponse(
contentId = content.contentId,
data = content.data,
domain = content.domain,
title = content.title,
memo = content.memo,
createdAt = content.createdAt.format(DateFormatters.DATE_FORMAT_YYYY_MM_DD),
thumbNail = content.thumbNail,
)
}
}
}

data class SharedCategoryResponse(
val categoryId: Long = 0L,
var categoryName: String,
var contentCount: Int = 0,
) {
companion object {
fun of(category: Category): SharedCategoryResponse {
return SharedCategoryResponse(
categoryId = category.categoryId,
categoryName = category.categoryName,
contentCount = category.contentCount,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.pokit.common.dto

import java.time.format.DateTimeFormatter

object DateFormatters {
val DATE_FORMAT_YYYY_MM_DD: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd")
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data class CreateContentRequest(
)
val data: String,
@field:NotBlank(message = "제목은 필수값입니다.")
@field:Size(min = 1, max = 20, message = "최대 20자까지만 입력 가능합니다.")
@field:Size(min = 1, max =1000, message = "최대 1000자까지만 입력 가능합니다.")
val title: String,
val categoryId: Int,
@field:Size(max = 100, message = "최대 100자까지만 입력 가능합니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ data class UpdateContentRequest(
@field:NotBlank(message = "링크는 필수값입니다.")
val data: String,
@field:NotBlank(message = "제목은 필수값입니다.")
@field:Size(min = 1, max = 20, message = "최대 20자까지만 입력 가능합니다.")
@field:Size(min = 1, max = 1000, message = "최대 1000자까지만 입력 가능합니다.")
val title: String,
val categoryId: Long,
@field:Size(max = 100, message = "최대 100자까지만 입력 가능합니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.pokit.out.persistence.category.impl

import com.pokit.category.model.Category
import com.pokit.category.model.OpenType
import com.pokit.category.port.out.CategoryPort
import com.pokit.out.persistence.category.persist.CategoryEntity
import com.pokit.out.persistence.category.persist.CategoryRepository
Expand All @@ -21,6 +22,9 @@ class CategoryAdapter(
override fun loadByIdAndUserId(id: Long, userId: Long): Category? =
categoryRepository.findByIdAndUserIdAndDeleted(id, userId, false)?.toDomain()

override fun loadById(id: Long): Category? =
categoryRepository.findByIdOrNull(id)?.toDomain()

override fun existsByNameAndUserId(name: String, userId: Long): Boolean =
categoryRepository.existsByNameAndUserIdAndDeleted(name, userId, false)

Expand All @@ -36,4 +40,8 @@ class CategoryAdapter(

override fun countByUserId(userId: Long): Int =
categoryRepository.countByUserIdAndDeleted(userId, false)

override fun loadByIdAndOpenType(id: Long, openType: OpenType): Category? =
categoryRepository.findByIdAndOpenTypeAndDeleted(id, openType, false)?.toDomain()

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.pokit.out.persistence.category.persist

import com.pokit.category.model.Category
import com.pokit.category.model.OpenType
import com.pokit.out.persistence.BaseEntity
import jakarta.persistence.*

Expand All @@ -22,6 +23,10 @@ class CategoryEntity(
@OneToOne
@JoinColumn(name = "image_id", foreignKey = ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
val image: CategoryImageEntity,

@Column(name = "open_type")
@Enumerated(EnumType.STRING)
var openType: OpenType = OpenType.PRIVATE,
) : BaseEntity() {

@Column(name = "is_deleted")
Expand All @@ -37,7 +42,8 @@ class CategoryEntity(
id = category.categoryId,
userId = category.userId,
name = category.categoryName,
image = CategoryImageEntity.of(category.categoryImage)
image = CategoryImageEntity.of(category.categoryImage),
openType = category.openType,
)
}
}
Expand All @@ -47,5 +53,6 @@ fun CategoryEntity.toDomain() = Category(
categoryName = this.name,
categoryImage = this.image.toDomain(),
userId = this.userId,
createdAt = this.createdAt
createdAt = this.createdAt,
openType = this.openType,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pokit.out.persistence.category.persist

import com.pokit.category.model.OpenType
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
import org.springframework.data.jpa.repository.JpaRepository
Expand All @@ -9,4 +10,5 @@ interface CategoryRepository : JpaRepository<CategoryEntity, Long> {
fun findByUserIdAndDeleted(userId: Long, deleted: Boolean, pageable: Pageable): Slice<CategoryEntity>
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?
}
Empty file.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.pokit.out.persistence.content.impl

import com.pokit.category.model.OpenType
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.port.out.ContentPort
import com.pokit.log.model.LogType
Expand Down Expand Up @@ -152,18 +154,43 @@ class ContentAdapter(
return SliceImpl(contentResults, pageable, hasNext)
}

override fun loadByCategoryIdAndOpenType(categoryId: Long, opentype: OpenType, pageable: Pageable): Slice<SharedContentResult> {
val contents = 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,
)
.offset(pageable.offset)
.limit((pageable.pageSize + 1).toLong())
.orderBy(getSortOrder(contentEntity.createdAt, "createdAt", pageable))
.fetch()

val hasNext = getHasNext(contents, pageable)

val contentResults = contents.map {
SharedContentResult.of(
it.toDomain(),
)
}

return SliceImpl(contentResults, pageable, hasNext)
}

override fun loadByContentIds(contentIds: List<Long>): List<Content> =
contentRepository.findByIdIn(contentIds)
.map { it.toDomain() }

private fun getHasNext(
contentEntityList: MutableList<Tuple>,
private fun <T> getHasNext(
items: MutableList<T>,
pageable: Pageable,
): Boolean {
var hasNext = false
if (contentEntityList.size > pageable.pageSize) {
if (items.size > pageable.pageSize) {
hasNext = true
contentEntityList.removeAt(contentEntityList.size - 1)
items.removeAt(items.size - 1)
}
return hasNext
}
Expand Down Expand Up @@ -215,10 +242,6 @@ class ContentAdapter(
}
}

private fun getSort(property: DateTimePath<LocalDateTime>, order: Sort.Order) =
if (order.isDescending) property.desc()
else property.asc()

private fun getSortOrder(property: DateTimePath<LocalDateTime>, sortField: String, pageable: Pageable): OrderSpecifier<*> {
val order = pageable.sort.getOrderFor(sortField)
?.direction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ interface CategoryUseCase {
fun getAllCategoryImages(): List<CategoryImage>
fun getCategories(userId: Long, pageable: Pageable, filterUncategorized: Boolean): Slice<CategoriesResponse>
fun getCategory(userId: Long, categoryId: Long): Category
fun getSharedCategory(categoryId: Long, userId: Long): Category
fun completeShare(categoryId: Long, userId: Long)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.pokit.category.port.out

import com.pokit.category.model.Category
import com.pokit.category.model.OpenType
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice

interface CategoryPort {
fun loadAllByUserId(userId: Long, pageable: Pageable): Slice<Category>
fun loadByIdAndUserId(id: Long, userId: Long): Category?
fun loadById(id: Long): Category?
fun existsByNameAndUserId(name: String, userId: Long): Boolean
fun persist(category: Category): Category
fun delete(category: Category)
fun countByUserId(userId: Long): Int
fun loadByIdAndOpenType(id: Long, openType: OpenType): Category?
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package com.pokit.category.port.service

import com.pokit.category.dto.CategoriesResponse
import com.pokit.category.dto.CategoryCommand
import com.pokit.category.dto.toCategoriesRespoonse
import com.pokit.category.dto.toCategoriesResponse
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.model.OpenType
import com.pokit.category.port.`in`.CategoryUseCase
import com.pokit.category.port.out.CategoryImagePort
import com.pokit.category.port.out.CategoryPort
Expand Down Expand Up @@ -53,6 +54,7 @@ class CategoryService(
categoryName = command.categoryName,
categoryImage = categoryImage,
userId = userId,
openType = OpenType.PRIVATE,
)
)
}
Expand Down Expand Up @@ -86,7 +88,7 @@ class CategoryService(
val contentCount = contentPort.fetchContentCountByCategoryId(category.categoryId)
category.copy(contentCount = contentCount)
}.map { category ->
category.toCategoriesRespoonse()
category.toCategoriesResponse()
}

val filteredCategories = if (filterUncategorized) {
Expand All @@ -101,6 +103,29 @@ class CategoryService(
override fun getCategory(userId: Long, categoryId: Long): Category =
categoryPort.loadCategoryOrThrow(categoryId, userId)

override fun getSharedCategory(categoryId: Long, userId: Long): Category {
val category = categoryPort.loadByIdAndOpenType(categoryId, OpenType.PUBLIC)
?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY)

if (category.userId == userId) {
throw InvalidRequestException(CategoryErrorCode.SHARE_ALREADY_EXISTS_CATEGORY)
}

category.apply {
contentCount = contentPort.fetchContentCountByCategoryId(categoryId)
}

return category
}

@Transactional
override fun completeShare(categoryId: Long, userId: Long) {
val category = categoryPort.loadCategoryOrThrow(categoryId, userId)
.completeShare()

categoryPort.persist(category)
}

override fun getAllCategoryImages(): List<CategoryImage> =
categoryImagePort.loadAll()

Expand All @@ -110,3 +135,7 @@ fun CategoryPort.loadCategoryOrThrow(categoryId: Long, userId: Long): Category {
return loadByIdAndUserId(categoryId, userId)
?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY)
}

fun CategoryPort.loadByIdOrThrow(categoryId: Long) =
loadById(categoryId)
?: throw NotFoundCustomException(CategoryErrorCode.NOT_FOUND_CATEGORY)
Loading

0 comments on commit 132626e

Please sign in to comment.