Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ import java.util.UUID
@UseCase
@Transactional(readOnly = true)
class BookUseCase(

@Qualifier(BookQueryServiceQualifier.ALADIN)
private val bookQueryService: BookQueryService,

private val userAuthService: UserAuthService,
private val userBookService: UserBookService,
private val bookManagementService: BookManagementService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,21 @@ package org.yapp.apis.readingrecord.usecase
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.transaction.annotation.Transactional
import org.springframework.beans.factory.annotation.Qualifier
import org.yapp.apis.auth.service.UserAuthService
import org.yapp.apis.book.service.UserBookService
import org.yapp.apis.readingrecord.dto.request.CreateReadingRecordRequest
import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
import org.yapp.apis.readingrecord.service.ReadingRecordService
import org.yapp.domain.readingrecord.ReadingRecordSortType
import org.yapp.globalutils.annotation.UseCase
import org.yapp.apis.book.constant.BookQueryServiceQualifier
import org.yapp.apis.book.service.BookQueryService
import org.yapp.domain.book.BookDomainService
import java.util.UUID
import java.util.*

@UseCase
@Transactional(readOnly = true)
class ReadingRecordUseCase(
private val readingRecordService: ReadingRecordService,
private val userAuthService: UserAuthService,
private val userBookService: UserBookService,
@Qualifier(BookQueryServiceQualifier.ALADIN)
private val bookQueryService: BookQueryService,
private val bookDomainService: BookDomainService
) {
@Transactional
fun createReadingRecord(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.yapp.apis.seed.controller

import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.yapp.apis.seed.dto.response.SeedStatsResponse
import org.yapp.apis.seed.usecase.SeedUseCase
import java.util.*

@RestController
@RequestMapping("/api/v1/seeds")
class SeedController(
private val seedUseCase: SeedUseCase
) : SeedControllerApi {

override fun getSeedStats(
@AuthenticationPrincipal userId: UUID
): ResponseEntity<SeedStatsResponse> {
val stats = seedUseCase.getSeedStats(userId)
return ResponseEntity.ok(stats)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.yapp.apis.seed.controller

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.yapp.apis.seed.dto.response.SeedStatsResponse
import org.yapp.globalutils.exception.ErrorResponse
import java.util.*

@Tag(name = "Seed", description = "씨앗(감정 태그) 관련 API")
interface SeedControllerApi {

@Operation(
summary = "씨앗 통계 조회",
description = "사용자가 모은 감정 태그별 씨앗 개수를 조회합니다."
)
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "씨앗 통계 조회 성공",
content = [Content(schema = Schema(implementation = SeedStatsResponse::class))]
),
ApiResponse(
responseCode = "404",
description = "사용자를 찾을 수 없음",
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
)
]
)
@GetMapping("/stats")
fun getSeedStats(
@AuthenticationPrincipal userId: UUID
): ResponseEntity<SeedStatsResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.yapp.apis.seed.dto.response

import org.yapp.domain.readingrecordtag.vo.TagStatsVO
import org.yapp.globalutils.tag.GeneralEmotionTagCategory

data class SeedStatsResponse private constructor(
val categories: List<SeedCategoryStats>
) {
data class SeedCategoryStats private constructor(
val name: String,
val count: Int
) {
companion object {
fun of(name: String, count: Int): SeedCategoryStats {
return SeedCategoryStats(name, count)
}
}
}

companion object {
fun from(tagStatsVO: TagStatsVO): SeedStatsResponse {
val categories = GeneralEmotionTagCategory.entries.map { category ->
SeedCategoryStats.of(
name = category.displayName,
count = tagStatsVO.categoryStats.getOrDefault(category, 0)
)
}
return SeedStatsResponse(categories)
}
}
}
21 changes: 21 additions & 0 deletions apis/src/main/kotlin/org/yapp/apis/seed/service/SeedService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.yapp.apis.seed.service

import org.springframework.stereotype.Service
import org.yapp.apis.seed.dto.response.SeedStatsResponse
import org.yapp.domain.readingrecordtag.ReadingRecordTagDomainService
import org.yapp.globalutils.tag.GeneralEmotionTagCategory
import java.util.*

@Service
class SeedService(
private val readingRecordTagDomainService: ReadingRecordTagDomainService
) {
fun getSeedStatsByUserId(userId: UUID): SeedStatsResponse {
val tagStatsVO = readingRecordTagDomainService.countTagsByUserIdAndCategories(
userId = userId,
categories = GeneralEmotionTagCategory.entries.map { it.displayName }
)

return SeedStatsResponse.from(tagStatsVO)
}
}
20 changes: 20 additions & 0 deletions apis/src/main/kotlin/org/yapp/apis/seed/usecase/SeedUseCase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.yapp.apis.seed.usecase

import org.springframework.transaction.annotation.Transactional
import org.yapp.apis.auth.service.UserAuthService
import org.yapp.apis.seed.dto.response.SeedStatsResponse
import org.yapp.apis.seed.service.SeedService
import org.yapp.globalutils.annotation.UseCase
import java.util.*

@UseCase
@Transactional(readOnly = true)
class SeedUseCase(
private val userAuthService: UserAuthService,
private val seedService: SeedService
) {
fun getSeedStats(userId: UUID): SeedStatsResponse {
userAuthService.validateUserExists(userId)
return seedService.getSeedStatsByUserId(userId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.yapp.domain.readingrecordtag

import org.yapp.domain.readingrecordtag.vo.TagStatsVO
import org.yapp.globalutils.annotation.DomainService
import java.util.*

@DomainService
class ReadingRecordTagDomainService(
private val readingRecordTagRepository: ReadingRecordTagRepository
) {
fun countTagsByUserIdAndCategories(userId: UUID, categories: List<String>): TagStatsVO {
val categoryStats = readingRecordTagRepository.countTagsByUserIdAndCategories(userId, categories)
return TagStatsVO.newInstance(categoryStats)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import java.util.UUID
interface ReadingRecordTagRepository {
fun saveAll(readingRecordTags: List<ReadingRecordTag>): List<ReadingRecordTag>
fun findByReadingRecordId(readingRecordId: UUID): List<ReadingRecordTag>
fun countTagsByUserIdAndCategories(userId: UUID, categories: List<String>): Map<String, Int>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.yapp.domain.readingrecordtag.vo

import org.yapp.globalutils.tag.GeneralEmotionTagCategory
import java.util.EnumMap

data class TagStatsVO private constructor(
val categoryStats: EnumMap<GeneralEmotionTagCategory, Int>
) {
init {
categoryStats.values.forEach { count ->
require(count >= 0) { "태그 개수는 0 이상이어야 합니다." }
}
}

companion object {
fun newInstance(categoryStats: Map<String, Int>): TagStatsVO {
val enumStats = EnumMap<GeneralEmotionTagCategory, Int>(GeneralEmotionTagCategory::class.java)

categoryStats.forEach { (displayName, count) ->
GeneralEmotionTagCategory.fromDisplayName(displayName)?.let { enumCategory ->
enumStats[enumCategory] = count
}
}

return TagStatsVO(enumStats)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.yapp.globalutils.tag

enum class GeneralEmotionTagCategory(
val displayName: String
) {
WARMTH("따뜻함"),
JOY("즐거움"),
TENSION("긴장감"),
SADNESS("슬픔");

companion object {
private val BY_DISPLAY_NAME = entries.associateBy { it.displayName }

fun fromDisplayName(displayName: String): GeneralEmotionTagCategory? {
return BY_DISPLAY_NAME[displayName]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.yapp.infra.readingrecordtag.repository

import java.util.*

interface JpaReadingRecordTagQuerydslRepository {
fun countTagsByUserIdAndCategories(userId: UUID, categories: List<String>): Map<String, Int>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository
import org.yapp.infra.readingrecordtag.entity.ReadingRecordTagEntity
import java.util.UUID

interface JpaReadingRecordTagRepository : JpaRepository<ReadingRecordTagEntity, UUID> {
interface JpaReadingRecordTagRepository : JpaRepository<ReadingRecordTagEntity, UUID>, JpaReadingRecordTagQuerydslRepository {
fun findByReadingRecordId(readingRecordId: UUID): List<ReadingRecordTagEntity>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.yapp.infra.readingrecordtag.repository.impl

import com.querydsl.core.types.dsl.BooleanExpression
import com.querydsl.jpa.impl.JPAQueryFactory
import org.springframework.stereotype.Repository
import org.yapp.infra.readingrecord.entity.QReadingRecordEntity
import org.yapp.infra.readingrecordtag.entity.QReadingRecordTagEntity
import org.yapp.infra.readingrecordtag.repository.JpaReadingRecordTagQuerydslRepository
import org.yapp.infra.tag.entity.QTagEntity
import org.yapp.infra.userbook.entity.QUserBookEntity
import java.util.*

@Repository
class JpaReadingRecordTagQuerydslRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : JpaReadingRecordTagQuerydslRepository {

private val readingRecordTag = QReadingRecordTagEntity.readingRecordTagEntity
private val readingRecord = QReadingRecordEntity.readingRecordEntity
private val userBook = QUserBookEntity.userBookEntity
private val tag = QTagEntity.tagEntity

override fun countTagsByUserIdAndCategories(
userId: UUID,
categories: List<String>
): Map<String, Int> {
if (categories.isEmpty()) {
return emptyMap()
}

val results = queryFactory
.select(tag.name, readingRecordTag.count())
.from(readingRecordTag)
.join(readingRecord).on(readingRecordTag.readingRecordId.eq(readingRecord.id))
.join(userBook).on(readingRecord.userBookId.eq(userBook.id))
.join(tag).on(readingRecordTag.tagId.eq(tag.id))
.where(
userBook.userIdEq(userId),
readingRecord.isNotDeleted(),
readingRecordTag.isNotDeleted(),
tag.nameIn(categories)
)
.groupBy(tag.name)
.fetch()

return results.associate { tuple ->
val tagName = tuple[tag.name] ?: ""
val count = tuple[readingRecordTag.count()]?.toInt() ?: 0
tagName to count
}
}

private fun QUserBookEntity.userIdEq(userId: UUID): BooleanExpression {
return this.userId.eq(userId)
}

private fun QReadingRecordEntity.isNotDeleted(): BooleanExpression {
return this.deletedAt.isNull
}

private fun QReadingRecordTagEntity.isNotDeleted(): BooleanExpression {
return this.deletedAt.isNull
}

private fun QTagEntity.nameIn(categories: List<String>): BooleanExpression {
return this.name.`in`(categories)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.yapp.infra.readingrecordtag.repository
package org.yapp.infra.readingrecordtag.repository.impl

import org.springframework.stereotype.Repository
import org.yapp.domain.readingrecordtag.ReadingRecordTag
import org.yapp.domain.readingrecordtag.ReadingRecordTagRepository
import org.yapp.infra.readingrecordtag.entity.ReadingRecordTagEntity
import org.yapp.infra.readingrecordtag.repository.JpaReadingRecordTagRepository
import java.util.*

@Repository
class ReadingRecordTagRepositoryImpl(
Expand All @@ -14,7 +16,11 @@ class ReadingRecordTagRepositoryImpl(
return jpaReadingRecordTagRepository.saveAll(entities).map { it.toDomain() }
}

override fun findByReadingRecordId(readingRecordId: java.util.UUID): List<ReadingRecordTag> {
override fun findByReadingRecordId(readingRecordId: UUID): List<ReadingRecordTag> {
return jpaReadingRecordTagRepository.findByReadingRecordId(readingRecordId).map { it.toDomain() }
}
}

override fun countTagsByUserIdAndCategories(userId: UUID, categories: List<String>): Map<String, Int> {
return jpaReadingRecordTagRepository.countTagsByUserIdAndCategories(userId, categories)
}
}