Skip to content

Commit

Permalink
[Feat/#430] 밀린 문제 ID 조회 API 추가 (#433)
Browse files Browse the repository at this point in the history
* feat: 밀린문제 ID 조회 API 1차 구현

* feat: 문제 단건 조회시 articleId 필드 응답 추가

* test: 문제 단건 조회 테스트 코드 수정(articleId 필드 추가)

* test: 밀린 문제 조회 컨트롤러 테스트 구현

* test: BrowseUndoneProblemsUseCaseTest 구현(fail)

* fix: @transactional 어노테이션 추가

* refactor: ArticleService, SubscriptionService 추가

* refactor: rename method to selectArticleIdsByWorkbookIdLimitDay

* refactor: rename field numOfReadArticle -> day

* feat: 밀린 문제가 articleId를 기준으로 랜덤 정렬 되도록 수정

* test: UC 코드 변경 테스트 코드 반영

* fix: 테스트를 위한 커밋 수정

* feat: 문제 id 조회시 Size 필드 응답 추가

* test: ProblemController 테스트 코드 리펙토링
  • Loading branch information
hun-ca authored Oct 17, 2024
1 parent 605cb07 commit ac18389
Show file tree
Hide file tree
Showing 30 changed files with 441 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import com.few.api.repo.dao.article.command.InsertFullArticleRecordCommand
import com.few.api.repo.dao.article.query.*
import com.few.api.repo.dao.article.record.*
import com.few.data.common.code.MemberType
import jooq.jooq_dsl.tables.ArticleIfo
import jooq.jooq_dsl.tables.ArticleMst
import jooq.jooq_dsl.tables.MappingWorkbookArticle
import jooq.jooq_dsl.tables.Member
import jooq.jooq_dsl.tables.*
import jooq.jooq_dsl.tables.MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE
import org.jooq.*
import org.jooq.impl.DSL
import org.springframework.cache.annotation.Cacheable
Expand Down Expand Up @@ -163,4 +161,23 @@ class ArticleDao(
)
.where(ArticleIfo.ARTICLE_IFO.ARTICLE_MST_ID.eq(query.articleId))
.and(ArticleIfo.ARTICLE_IFO.DELETED_AT.isNull)

fun selectArticleIdsByWorkbookIdLimitDay(query: SelectAritlceIdByWorkbookIdAndDayQuery): ArticleIdRecord {
return selectArticleIdByWorkbookIdLimitDayQuery(query)
.fetch()
.map { it[MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID] }
.let { ArticleIdRecord(it) }
}

fun selectArticleIdByWorkbookIdLimitDayQuery(query: SelectAritlceIdByWorkbookIdAndDayQuery) =
dslContext
.select(MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID)
.from(MAPPING_WORKBOOK_ARTICLE)
.join(ArticleMst.ARTICLE_MST)
.on(MAPPING_WORKBOOK_ARTICLE.ARTICLE_ID.eq(ArticleMst.ARTICLE_MST.ID))
.where(MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(query.workbookId))
.and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull)
.orderBy(MAPPING_WORKBOOK_ARTICLE.DAY_COL.asc())
.limit(query.day)
.query
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.repo.dao.article.query

data class SelectAritlceIdByWorkbookIdAndDayQuery(
val workbookId: Long,
val day: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.repo.dao.article.record

data class ArticleIdRecord(
val articleIds: List<Long>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.few.api.repo.dao.problem

import com.few.api.repo.dao.problem.command.InsertProblemsCommand
import com.few.api.repo.dao.problem.query.SelectProblemAnswerQuery
import com.few.api.repo.dao.problem.query.SelectProblemIdByArticleIdsQuery
import com.few.api.repo.dao.problem.query.SelectProblemQuery
import com.few.api.repo.dao.problem.query.SelectProblemsByArticleIdQuery
import com.few.api.repo.dao.problem.record.ProblemIdAndArticleIdRecord
import com.few.api.repo.dao.problem.record.ProblemIdsRecord
import com.few.api.repo.dao.problem.record.SelectProblemAnswerRecord
import com.few.api.repo.dao.problem.record.SelectProblemRecord
Expand All @@ -28,7 +30,8 @@ class ProblemDao(
Problem.PROBLEM.ID.`as`(SelectProblemRecord::id.name),
Problem.PROBLEM.TITLE.`as`(SelectProblemRecord::title.name),
DSL.field("JSON_UNQUOTE({0})", String::class.java, Problem.PROBLEM.CONTENTS)
.`as`(SelectProblemRecord::contents.name)
.`as`(SelectProblemRecord::contents.name),
Problem.PROBLEM.ARTICLE_ID.`as`(SelectProblemRecord::articleId.name)
)
.from(Problem.PROBLEM)
.where(Problem.PROBLEM.ID.eq(query.problemId))
Expand Down Expand Up @@ -77,4 +80,18 @@ class ProblemDao(
.set(Problem.PROBLEM.CONTENTS, JSON.valueOf(contentsJsonMapper.toJson(it.contents)))
.set(Problem.PROBLEM.ANSWER, it.answer)
.set(Problem.PROBLEM.EXPLANATION, it.explanation)

fun selectProblemIdByArticleIds(query: SelectProblemIdByArticleIdsQuery): List<ProblemIdAndArticleIdRecord> {
return selectProblemIdByArticleIdsQuery(query)
.fetchInto(ProblemIdAndArticleIdRecord::class.java)
}

fun selectProblemIdByArticleIdsQuery(query: SelectProblemIdByArticleIdsQuery) = dslContext
.select(
Problem.PROBLEM.ID.`as`(ProblemIdAndArticleIdRecord::problemId.name),
Problem.PROBLEM.ARTICLE_ID.`as`(ProblemIdAndArticleIdRecord::articleId.name)
)
.from(Problem.PROBLEM)
.where(Problem.PROBLEM.ARTICLE_ID.`in`(query.articleIds))
.and(Problem.PROBLEM.DELETED_AT.isNull)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.few.api.repo.dao.problem

import com.few.api.repo.dao.problem.command.InsertSubmitHistoryCommand
import com.few.api.repo.dao.problem.query.SelectSubmittedProblemIdsQuery
import com.few.api.repo.dao.problem.record.SubmittedProblemIdsRecord
import jooq.jooq_dsl.Tables.SUBMIT_HISTORY
import org.jooq.DSLContext
import org.springframework.stereotype.Repository
Expand All @@ -24,4 +26,18 @@ class SubmitHistoryDao(
.set(SUBMIT_HISTORY.MEMBER_ID, command.memberId)
.set(SUBMIT_HISTORY.SUBMIT_ANS, command.submitAns)
.set(SUBMIT_HISTORY.IS_SOLVED, command.isSolved)

fun selectProblemIdByProblemIds(query: SelectSubmittedProblemIdsQuery): SubmittedProblemIdsRecord {
return selectProblemIdByProblemIdsQuery(query)
.fetch()
.map { it[SUBMIT_HISTORY.PROBLEM_ID] }
.let { SubmittedProblemIdsRecord(it) }
}

fun selectProblemIdByProblemIdsQuery(query: SelectSubmittedProblemIdsQuery) = dslContext
.select(SUBMIT_HISTORY.PROBLEM_ID)
.from(SUBMIT_HISTORY)
.where(SUBMIT_HISTORY.PROBLEM_ID.`in`(query.problemIds))
.and(SUBMIT_HISTORY.MEMBER_ID.eq(query.memberId))
.and(SUBMIT_HISTORY.DELETED_AT.isNull)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.repo.dao.problem.query

data class SelectProblemIdByArticleIdsQuery(
val articleIds: Set<Long>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.repo.dao.problem.query

data class SelectSubmittedProblemIdsQuery(
val memberId: Long,
val problemIds: List<Long>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.repo.dao.problem.record

data class ProblemIdAndArticleIdRecord(
val problemId: Long,
val articleId: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ data class SelectProblemRecord(
val id: Long,
val title: String,
val contents: String,
val articleId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.repo.dao.problem.record

data class SubmittedProblemIdsRecord(
val problemIds: List<Long>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,18 @@ class SubscriptionDao(
.from(SUBSCRIPTION)
.where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId))
.and(SUBSCRIPTION.DELETED_AT.isNull)

fun selectWorkbookIdAndProgressByMember(query: SelectSubscriptionSendStatusQuery): List<SubscriptionProgressRecord> {
return selectWorkbookIdAndProgressByMemberQuery(query)
.fetchInto(SubscriptionProgressRecord::class.java)
}

fun selectWorkbookIdAndProgressByMemberQuery(query: SelectSubscriptionSendStatusQuery) =
dslContext.select(
SUBSCRIPTION.TARGET_WORKBOOK_ID.`as`(SubscriptionProgressRecord::workbookId.name),
SUBSCRIPTION.PROGRESS.add(1).`as`(SubscriptionProgressRecord::day.name)
)
.from(SUBSCRIPTION)
.where(SUBSCRIPTION.MEMBER_ID.eq(query.memberId))
.and(SUBSCRIPTION.DELETED_AT.isNull)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.repo.dao.subscription.record

data class SubscriptionProgressRecord(
val workbookId: Long,
val day: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.few.api.domain.problem.service

import com.few.api.domain.problem.service.dto.BrowseArticleIdInDto
import com.few.api.repo.dao.article.ArticleDao
import com.few.api.repo.dao.article.query.SelectAritlceIdByWorkbookIdAndDayQuery
import org.springframework.stereotype.Service

@Service
class ArticleService(
private val articleDao: ArticleDao,
) {

fun browseArticleIdByWorkbookIdLimitDay(inDto: BrowseArticleIdInDto): List<Long> {
return articleDao.selectArticleIdsByWorkbookIdLimitDay(
SelectAritlceIdByWorkbookIdAndDayQuery(
inDto.workbookId,
inDto.day
)
).articleIds
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.few.api.domain.problem.service

import com.few.api.domain.problem.service.dto.BrowseWorkbookIdAndProgressInDto
import com.few.api.domain.problem.service.dto.SubscriptionProgressOutDto
import com.few.api.repo.dao.subscription.SubscriptionDao
import com.few.api.repo.dao.subscription.query.SelectSubscriptionSendStatusQuery
import org.springframework.stereotype.Component

@Component
class SubscriptionService(
private val subscriptionDao: SubscriptionDao,
) {

fun browseWorkbookIdAndProgress(inDto: BrowseWorkbookIdAndProgressInDto): List<SubscriptionProgressOutDto> {
val subscriptionProgresses = subscriptionDao.selectWorkbookIdAndProgressByMember(
SelectSubscriptionSendStatusQuery(inDto.memberId)
)

return subscriptionProgresses.map { SubscriptionProgressOutDto(it.workbookId, it.day) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.domain.problem.service.dto

data class BrowseArticleIdInDto(
val workbookId: Long,
val day: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.domain.problem.service.dto

data class BrowseWorkbookIdAndProgressInDto(
val memberId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.few.api.domain.problem.service.dto

data class SubscriptionProgressOutDto(
val workbookId: Long,
val day: Int,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.problem.ProblemDao
import com.few.api.repo.dao.problem.query.SelectProblemsByArticleIdQuery
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class BrowseProblemsUseCase(
private val problemDao: ProblemDao,
) {

@Transactional(readOnly = true)
fun execute(useCaseIn: BrowseProblemsUseCaseIn): BrowseProblemsUseCaseOut {
problemDao.selectProblemsByArticleId(SelectProblemsByArticleIdQuery(useCaseIn.articleId))
?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.few.api.domain.problem.usecase

import com.few.api.domain.problem.service.ArticleService
import com.few.api.domain.problem.service.SubscriptionService
import com.few.api.domain.problem.service.dto.BrowseArticleIdInDto
import com.few.api.domain.problem.service.dto.BrowseWorkbookIdAndProgressInDto
import com.few.api.domain.problem.usecase.dto.BrowseProblemsUseCaseOut
import com.few.api.domain.problem.usecase.dto.BrowseUndoneProblemsUseCaseIn
import com.few.api.exception.common.NotFoundException
import com.few.api.repo.dao.problem.ProblemDao
import com.few.api.repo.dao.problem.SubmitHistoryDao
import com.few.api.repo.dao.problem.query.SelectProblemIdByArticleIdsQuery
import com.few.api.repo.dao.problem.query.SelectSubmittedProblemIdsQuery
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class BrowseUndoneProblemsUseCase(
private val problemDao: ProblemDao,
private val subscriptionService: SubscriptionService,
private val articleService: ArticleService,
private val submitHistoryDao: SubmitHistoryDao,
) {

@Transactional(readOnly = true)
fun execute(useCaseIn: BrowseUndoneProblemsUseCaseIn): BrowseProblemsUseCaseOut {
/**
* 유저가 구독한 워크북들에 속한 아티클 개수를 조회함
* 이때 아티클 개수는 현 시점 기준으로 이메일이 전송된 아티클 개수까지만 조회함
*/
val subscriptionProgresses = subscriptionService.browseWorkbookIdAndProgress(
BrowseWorkbookIdAndProgressInDto(useCaseIn.memberId)
).takeIf { it.isNotEmpty() } ?: throw NotFoundException("subscribe.workbook.notexist")

/**
* 위에서 조회한 워크부에 속한 아티클 개수에 대해 article_id 들을 조회함
*/
val sentArticleIds = subscriptionProgresses.flatMap { subscriptionProgress ->
articleService.browseArticleIdByWorkbookIdLimitDay(
BrowseArticleIdInDto(
subscriptionProgress.workbookId,
subscriptionProgress.day
)
)
}.toSet()

/**
* 위에서 구한 아티클에 속한 모든 problem_id, article_id 조합을 조회함
*/
val allProblemIdsAndArticleIdsToBeSolved = problemDao.selectProblemIdByArticleIds(
SelectProblemIdByArticleIdsQuery(sentArticleIds)
)

/**
* 위에서 구한 문제들에 대해 풀이 이력이 존재하는 problem_id만 추출 후
* 유저가 풀어야 할 전체 problem_id에 대해 여집합 연산
*/
val allProblemIdsToBeSolved = allProblemIdsAndArticleIdsToBeSolved.map { it.problemId }
val submittedProblemIds = submitHistoryDao.selectProblemIdByProblemIds(
SelectSubmittedProblemIdsQuery(useCaseIn.memberId, allProblemIdsToBeSolved)
).problemIds

val unsubmittedProblemIdAndArticleIds: Map<Long, List<Long>> = allProblemIdsAndArticleIdsToBeSolved
.filter { it.problemId !in submittedProblemIds }
.groupBy { it.articleId }
.mapValues { entry -> entry.value.map { it.problemId } }

/**
* 결과를 article_id를 기준으로 랜덤화한 뒤 problem_id를 순차적으로 리턴함
*/
val randomArticleIds = unsubmittedProblemIdAndArticleIds.keys.shuffled()
val problemIdsRandomizedByArticleId = mutableListOf<Long>()

randomArticleIds.forEach { articleId ->
unsubmittedProblemIdAndArticleIds[articleId]?.let { problemIds ->
problemIdsRandomizedByArticleId.addAll(problemIds)
}
}

return BrowseProblemsUseCaseOut(problemIdsRandomizedByArticleId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class ReadProblemUseCase(
return ReadProblemUseCaseOut(
id = record.id,
title = record.title,
contents = contents
contents = contents,
articleId = record.articleId
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.domain.problem.usecase.dto

data class BrowseUndoneProblemsUseCaseIn(
val memberId: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class ReadProblemUseCaseOut(
val id: Long,
val title: String,
val contents: List<ReadProblemContentsUseCaseOutDetail>,
val articleId: Long,
)

data class ReadProblemContentsUseCaseOutDetail(
Expand Down
Loading

0 comments on commit ac18389

Please sign in to comment.