Skip to content

Commit

Permalink
[feat #202] 추천 컨텐츠 조회 API (#203)
Browse files Browse the repository at this point in the history
* feat : 추천 컨텐츠 조회 api

* feat : 추천 컨텐츠 조회 로직

* feat : 컨텐츠 조회 응답 dto에 키워드 필드 추가

* feat : 추천 컨텐츠 조회 쿼리 구현
  • Loading branch information
dlswns2480 authored Dec 16, 2024
1 parent 8a38570 commit c00a36c
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.pokit.category.v2.dto.request
import com.pokit.category.dto.CategoryCommand
import com.pokit.category.model.OpenType
import com.pokit.user.model.InterestType
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size

Expand All @@ -11,8 +12,10 @@ data class CreateCategoryRequestV2(
@field:Size(min = 1, max = 10, message = "최대 10자까지 입력 가능합니다.")
val categoryName: String,
val categoryImageId: Int,
@field:Schema(description = "PUBLIC / PRIVATE 중 하나여야 합니다.")
val openType: String,
val keywordType: String,
@field:Schema(description = "관심사 리스트랑 같은 리스트 쓰니 그 중 하나 선택")
val keywordType: String = "default",
)

internal fun CreateCategoryRequestV2.toDto() = CategoryCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,28 @@ class ContentController(
@PathVariable("contentId") contentId: Long,
@RequestBody request: UpdateThumbnailRequest
): ResponseEntity<Content> {
return contentUseCase.updateThumbnail(user.id, contentId,request.toDto())
return contentUseCase.updateThumbnail(user.id, contentId, request.toDto())
.wrapOk()
}

@GetMapping("/recommended")
@Operation(summary = "추천 컨텐츠 목록 조회 API", description = "keyword 생략이나 비워서 보내면 전체보기 적용")
fun getRecommendedContents(
@AuthenticationPrincipal user: PrincipalUser,
@PageableDefault(
page = 0,
size = 10,
sort = ["createdAt"],
direction = Sort.Direction.DESC
) pageable: Pageable,
@RequestParam("keyword") keyword: String?,
): ResponseEntity<SliceResponseDto<ContentsResponse>> {
return contentUseCase.getRecommendedContent(user.id, keyword, pageable)
.map { it.toResponse() }
.wrapSlice()
.wrapOk()

}

}

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ data class ContentsResponse(
val createdAt: String,
val isRead: Boolean,
val thumbNail: String,
val isFavorite: Boolean
val isFavorite: Boolean,
val keyword: String,
)

fun ContentsResult.toResponse(): ContentsResponse {
Expand All @@ -32,6 +33,7 @@ fun ContentsResult.toResponse(): ContentsResponse {
createdAt = this.createdAt.format(formatter),
isRead = this.isRead,
thumbNail = this.thumbNail,
isFavorite = this.isFavorite
isFavorite = this.isFavorite,
keyword = this.keyword,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.pokit.out.persistence.content.persist.ContentRepository
import com.pokit.out.persistence.content.persist.QContentEntity.contentEntity
import com.pokit.out.persistence.content.persist.toDomain
import com.pokit.out.persistence.log.persist.QUserLogEntity.userLogEntity
import com.pokit.user.model.InterestType
import com.querydsl.core.Tuple
import com.querydsl.core.types.OrderSpecifier
import com.querydsl.core.types.Predicate
Expand Down Expand Up @@ -214,6 +215,38 @@ class ContentAdapter(
contentRepository.updateCategoryId(contentIds, categoryId)
}

override fun loadAllByKeyword(userId: Long, searchKeywords: List<InterestType>, pageable: Pageable): Slice<ContentsResult> {
val contents = queryFactory.select(contentEntity, categoryEntity.name, categoryEntity.keyword)
.from(contentEntity)
.join(categoryEntity).on(contentEntity.categoryId.eq(categoryEntity.id))
.where(
categoryEntity.openType.eq(OpenType.PUBLIC),
categoryEntity.userId.ne(userId),
categoryEntity.keyword.`in`(searchKeywords),
categoryEntity.deleted.isFalse,
contentEntity.deleted.isFalse
)
.offset(pageable.offset)
.groupBy(contentEntity)
.limit((pageable.pageSize + 1).toLong())
.orderBy(getSortOrder(contentEntity.createdAt, "createdAt", pageable))
.fetch()

val hasNext = getHasNext(contents, pageable)

val contentsResults = contents.map {
ContentsResult.of(
it[contentEntity]!!.toDomain(),
it[categoryEntity.name]!!,
0,
0,
it[categoryEntity.keyword]!!.kor
)
}

return SliceImpl(contentsResults, pageable, hasNext)
}

override fun loadByContentIds(contentIds: List<Long>): List<Content> =
contentRepository.findByIdIn(contentIds)
.map { it.toDomain() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ interface ContentUseCase {
fun categorize(userId: Long, command: CategorizeCommand)

fun updateThumbnail(userId: Long, contentId: Long, thumbnail: String): Content

fun getRecommendedContent(userId: Long, keyword: String?, pageable: Pageable): Slice<ContentsResult>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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
import com.pokit.user.model.InterestType
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice

Expand Down Expand Up @@ -52,4 +53,6 @@ interface ContentPort {
fun loadAllByUserIdAndContentIds(userId: Long, contentIds: List<Long>): List<Content>

fun updateCategoryId(contents: List<Content>, categoryId: Long)

fun loadAllByKeyword(userId: Long, searchKeywords: List<InterestType>, pageable: Pageable): Slice<ContentsResult>
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import com.pokit.content.port.out.ContentPort
import com.pokit.log.model.LogType
import com.pokit.log.model.UserLog
import com.pokit.log.port.out.UserLogPort
import com.pokit.user.model.InterestType
import com.pokit.user.model.User
import com.pokit.user.port.out.InterestPort
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
Expand All @@ -42,7 +44,8 @@ class ContentService(
private val categoryPort: CategoryPort,
private val userLogPort: UserLogPort,
private val publisher: ApplicationEventPublisher,
private val contentCountPort: ContentCountPort
private val contentCountPort: ContentCountPort,
private val interestPort: InterestPort,
) : ContentUseCase {
companion object {
private const val MIN_CONTENT_COUNT = 3
Expand Down Expand Up @@ -196,6 +199,15 @@ class ContentService(
return contentPort.persist(content)
}

override fun getRecommendedContent(userId: Long, keyword: String?, pageable: Pageable): Slice<ContentsResult> {
val searchKeyword = keyword?.let {
listOf(InterestType.of(it))
} ?: interestPort.loadByUserId(userId).map {
it.interestType
}
return contentPort.loadAllByKeyword(userId, searchKeyword, pageable)
}

private fun verifyContent(userId: Long, contentId: Long): Content {
return contentPort.loadByUserIdAndId(userId, contentId)
?: throw NotFoundCustomException(ContentErrorCode.NOT_FOUND_CONTENT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,17 @@ data class ContentsResult(
val createdAt: LocalDateTime,
val isRead: Boolean,
val thumbNail: String,
val isFavorite: Boolean
val isFavorite: Boolean,
val keyword: String
) {
companion object {
fun of(content: Content, categoryName: String, isRead: Long, isFavorite: Long): ContentsResult {
fun of(
content: Content,
categoryName: String,
isRead: Long,
isFavorite: Long,
keyword: String = "default"
): ContentsResult {
return ContentsResult(
contentId = content.id,
category = RemindCategory(content.categoryId, categoryName),
Expand All @@ -31,7 +38,8 @@ data class ContentsResult(
createdAt = content.createdAt,
isRead = isRead > 0,
thumbNail = content.thumbNail ?: ContentDefault.THUMB_NAIL,
isFavorite = isFavorite > 0
isFavorite = isFavorite > 0,
keyword = keyword
)
}
}
Expand Down

0 comments on commit c00a36c

Please sign in to comment.