Skip to content

Commit

Permalink
change data type of contribution count from int to long (#219)
Browse files Browse the repository at this point in the history
## Checklist
- [ ] 충분한 양의 자동화 테스트를 작성했는가?
  - 계단정복지도 서비스는 사이드 프로젝트로 진행되는 만큼 충분한 QA 없이 배포되는 경우가 많습니다. 따라서 자동화 테스트를 꼼꼼하게 작성하는 것이 서비스 품질을 유지하는 데 매우 중요합니다.
  • Loading branch information
bellatoris authored Oct 29, 2023
1 parent ada091d commit d1df5f9
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 25 deletions.
4 changes: 4 additions & 0 deletions app-server/subprojects/api/scc-api/api-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,10 @@ components:
type: array
items:
$ref: '#/components/schemas/ChallengeRankDto'
contributionCountForNextRank:
type: integer
format: int64
description: 다음 랭크에 도달하기 위해 필요한 contribution 수. ex) 10개 정복
hasJoined:
type: boolean
description: 내가 참여하고 있는지에 대한 정보
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package club.staircrusher.challenge.application.port.`in`

import club.staircrusher.user.domain.model.User

data class ChallengeParticipant(
val userId: String,
val nickname: String,
)
fun User.toDomainModel() = ChallengeParticipant(
userId = id,
nickname = nickname,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package club.staircrusher.challenge.application.port.`in`.result

import club.staircrusher.challenge.application.port.`in`.ChallengeParticipant

data class WithUserInfo<T>(
val value: T,
val challengeParticipant: ChallengeParticipant?,
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package club.staircrusher.challenge.application.port.`in`.use_case

import club.staircrusher.challenge.application.port.`in`.result.WithUserInfo
import club.staircrusher.challenge.application.port.`in`.toDomainModel
import club.staircrusher.challenge.application.port.out.persistence.ChallengeRankRepository
import club.staircrusher.challenge.application.port.out.persistence.ChallengeRepository
import club.staircrusher.challenge.domain.model.ChallengeRank
import club.staircrusher.stdlib.di.annotation.Component
import club.staircrusher.stdlib.domain.SccDomainException
import club.staircrusher.stdlib.persistence.TransactionManager
import club.staircrusher.user.application.port.`in`.UserApplicationService

/**
* Get the leaderboard of a challenge which shows only the top 10 users.
Expand All @@ -18,15 +21,19 @@ class GetChallengeLeaderboardUseCase(
private val transactionManager: TransactionManager,
private val challengeRepository: ChallengeRepository,
private val challengeRankRepository: ChallengeRankRepository,
private val userApplicationService: UserApplicationService,
) {
companion object {
const val NUMBER_OF_TOP_RANKER = 10
}

fun handle(challengeId: String): List<ChallengeRank> = transactionManager.doInTransaction {
fun handle(challengeId: String): List<WithUserInfo<ChallengeRank>> = transactionManager.doInTransaction {
val challenge = challengeRepository.findByIdOrNull(challengeId) ?: throw SccDomainException("잘못된 챌린지입니다.")
val leaderboards = challengeRankRepository.findTopNUsers(challenge.id, NUMBER_OF_TOP_RANKER)
val users = userApplicationService.getUsers(leaderboards.map { it.userId }).associateBy { it.id }

leaderboards
leaderboards.map { challengeRank ->
WithUserInfo(challengeRank, users[challengeRank.userId]!!.toDomainModel())
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package club.staircrusher.challenge.application.port.`in`.use_case

import club.staircrusher.challenge.application.port.`in`.ChallengeService
import club.staircrusher.challenge.application.port.`in`.result.WithUserInfo
import club.staircrusher.challenge.application.port.`in`.toDomainModel
import club.staircrusher.challenge.application.port.out.persistence.ChallengeRankRepository
import club.staircrusher.challenge.application.port.out.persistence.ChallengeRepository
import club.staircrusher.challenge.domain.model.ChallengeRank
Expand All @@ -17,15 +19,14 @@ class GetChallengeRankUseCase(
private val challengeRankRepository: ChallengeRankRepository,
private val challengeService: ChallengeService,
) {
fun handle(challengeId: String, userId: String): ChallengeRank? = transactionManager.doInTransaction {
userApplicationService.getUser(userId) ?: throw SccDomainException("잘못된 계정입니다.")

fun handle(challengeId: String, userId: String): WithUserInfo<ChallengeRank>? = transactionManager.doInTransaction {
val user = userApplicationService.getUser(userId) ?: throw SccDomainException("잘못된 계정입니다.")
val challenge = challengeRepository.findByIdOrNull(challengeId) ?: throw SccDomainException("잘못된 챌린지입니다.")
if (!challengeService.hasJoined(userId, challengeId)) {
throw SccDomainException("참여하지 않은 챌린지 입니다.")
}

// if the user does not have rank yet, return null and let the user know that the rank will be updated soon
challengeRankRepository.findByUserId(challenge.id, userId)
challengeRankRepository.findByUserId(challenge.id, userId)?.let { WithUserInfo(it, user.toDomainModel()) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class GetCountForNextChallengeRankUseCase(
private val challengeRankRepository: ChallengeRankRepository,
private val challengeService: ChallengeService,
) {
fun handle(challengeId: String, userId: String): Int? = transactionManager.doInTransaction {
fun handle(challengeId: String, userId: String): Long? = transactionManager.doInTransaction {
userApplicationService.getUser(userId) ?: throw SccDomainException("잘못된 계정입니다.")
val challenge = challengeRepository.findByIdOrNull(challengeId) ?: throw SccDomainException("잘못된 챌린지입니다.")
if (!challengeService.hasJoined(userId, challengeId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ class UpdateChallengeRanksUseCase(
id = EntityIdGenerator.generateRandom(),
challengeId = challenge.id,
userId = userId,
contributionCount = contributions.size,
contributionCount = contributions.size.toLong(),
rank = -1,
createdAt = now,
updatedAt = now,
)

challengeRank.apply {
contributionCount = contributions.size
contributionCount = contributions.size.toLong()
updatedAt = now
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ interface ChallengeRankRepository : EntityRepository<ChallengeRank, String> {
fun findByUserId(challengeId: String, userId: String): ChallengeRank?
fun findByRank(challengeId: String, rank: Long): ChallengeRank?
fun findNextRank(challengeId: String, rank: Long): ChallengeRank?
fun findByContributionCount(challengeId: String, contributionCount: Int): ChallengeRank?
fun findByContributionCount(challengeId: String, contributionCount: Long): ChallengeRank?
fun findLastRank(challengeId: String): Long?
fun findAll(challengeId: String): List<ChallengeRank>
fun removeAll(challengeId: String): Unit
fun removeAll(challengeId: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class ChallengeRank(
val id: String,
val challengeId: String,
val userId: String,
var contributionCount: Int,
var contributionCount: Long,
var rank: Long,
var createdAt: Instant,
var updatedAt: Instant,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package club.staircrusher.challenge.infra.adapter.`in`.controller

import club.staircrusher.api.spec.dto.ChallengeRankDto
import club.staircrusher.api.spec.dto.ChallengeStatusDto
import club.staircrusher.api.spec.dto.GetChallengeRequestDto
import club.staircrusher.api.spec.dto.GetChallengeResponseDto
Expand All @@ -9,8 +8,11 @@ import club.staircrusher.api.spec.dto.JoinChallengeRequestDto
import club.staircrusher.api.spec.dto.JoinChallengeResponseDto
import club.staircrusher.api.spec.dto.ListChallengesRequestDto
import club.staircrusher.api.spec.dto.ListChallengesResponseDto
import club.staircrusher.challenge.application.port.`in`.use_case.GetChallengeLeaderboardUseCase
import club.staircrusher.challenge.application.port.`in`.use_case.GetChallengeRankUseCase
import club.staircrusher.challenge.application.port.`in`.use_case.GetChallengeUseCase
import club.staircrusher.challenge.application.port.`in`.use_case.GetChallengeWithInvitationCodeUseCase
import club.staircrusher.challenge.application.port.`in`.use_case.GetCountForNextChallengeRankUseCase
import club.staircrusher.challenge.application.port.`in`.use_case.JoinChallengeUseCase
import club.staircrusher.challenge.application.port.`in`.use_case.ListChallengesUseCase
import club.staircrusher.challenge.infra.adapter.out.persistence.sqldelight.toDto
Expand All @@ -25,6 +27,9 @@ import java.time.Clock
class ChallengeController(
private val getChallengeUseCase: GetChallengeUseCase,
private val getChallengeWithInvitationCodeUseCase: GetChallengeWithInvitationCodeUseCase,
private val getChallengeRankUseCase: GetChallengeRankUseCase,
private val getChallengeLeaderboardUseCase: GetChallengeLeaderboardUseCase,
private val getContributionCountForNextChallengeRankUseCase: GetCountForNextChallengeRankUseCase,
private val joinChallengeUseCase: JoinChallengeUseCase,
private val listChallengesUseCase: ListChallengesUseCase,
private val clock: Clock
Expand All @@ -35,17 +40,34 @@ class ChallengeController(
authentication: SccAppAuthentication?,
): GetChallengeResponseDto {
val result = getChallengeUseCase.handle(userId = authentication?.principal, challengeId = request.challengeId)
val ranks = getChallengeLeaderboardUseCase.handle(challengeId = request.challengeId)
val myRank = if (result.hasJoined && authentication != null) {
getChallengeRankUseCase.handle(
challengeId = result.challenge.id,
userId = authentication.principal
)
} else {
null
}
val contributionCountForNextRank = if (result.hasJoined && authentication != null) {
getContributionCountForNextChallengeRankUseCase.handle(
challengeId = result.challenge.id,
userId = authentication.principal
)
} else {
null
}
return GetChallengeResponseDto(
challenge = result.challenge.toDto(
participationsCount = result.participationsCount,
contributionsCount = result.contributionsCount,
criteriaTime = clock.instant()
),
ranks = listOf(),
ranks = ranks.map { (rank, user) -> rank.toDto(user!!.nickname) },
hasJoined = result.hasJoined,
hasPasscode = result.challenge.passcode != null,
// TODO: rank 반영
myRank = if (result.hasJoined) ChallengeRankDto(rank = 0, contributionCount = 0, nickname = "") else null
myRank = myRank?.let { (rank, user) -> rank.toDto(user!!.nickname) },
contributionCountForNextRank = contributionCountForNextRank,
)
}

Expand All @@ -58,17 +80,34 @@ class ChallengeController(
userId = authentication.principal,
invitationCode = request.invitationCode
)
val ranks = getChallengeLeaderboardUseCase.handle(result.challenge.id)
val myRank = if (result.hasJoined) {
getChallengeRankUseCase.handle(
challengeId = result.challenge.id,
userId = authentication.principal
)
} else {
null
}
val contributionCountForNextRank = if (result.hasJoined) {
getContributionCountForNextChallengeRankUseCase.handle(
challengeId = result.challenge.id,
userId = authentication.principal
)
} else {
null
}
return GetChallengeResponseDto(
challenge = result.challenge.toDto(
participationsCount = result.participationsCount,
contributionsCount = result.contributionsCount,
criteriaTime = clock.instant()
),
ranks = listOf(),
ranks = ranks.map { (rank, user) -> rank.toDto(user!!.nickname) },
hasJoined = result.hasJoined,
hasPasscode = result.challenge.passcode != null,
// TODO: rank 반영
myRank = if (result.hasJoined) ChallengeRankDto(rank = 0, contributionCount = 0, nickname = "") else null
myRank = myRank?.let { (rank, user) -> rank.toDto(user!!.nickname) },
contributionCountForNextRank = contributionCountForNextRank,
)
}

Expand All @@ -83,13 +122,14 @@ class ChallengeController(
challengeId = request.challengeId,
passcode = request.passcode
)
val ranks = getChallengeLeaderboardUseCase.handle(challengeId = request.challengeId)
return JoinChallengeResponseDto(
challenge = result.challenge.toDto(
participationsCount = result.participationsCount,
contributionsCount = result.contributionsCount,
criteriaTime = clock.instant()
),
ranks = listOf()
ranks = ranks.map { (rank, user) -> rank.toDto(user!!.nickname) },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ChallengeRankRepository(
return queries.findLastRank(challengeId).executeAsOneOrNull()?.max
}

override fun findByContributionCount(challengeId: String, contributionCount: Int): ChallengeRank? {
override fun findByContributionCount(challengeId: String, contributionCount: Long): ChallengeRank? {
return queries.findByContributionCount(challengeId, contributionCount)
.executeAsOneOrNull()
?.toDomainModel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
package club.staircrusher.challenge.infra.adapter.out.persistence.sqldelight

import club.staircrusher.api.spec.dto.ChallengeDto
import club.staircrusher.api.spec.dto.ChallengeRankDto
import club.staircrusher.api.spec.dto.ChallengeStatusDto
import club.staircrusher.api.spec.dto.EpochMillisTimestamp
import club.staircrusher.api.spec.dto.ListChallengesItemDto
import club.staircrusher.challenge.domain.model.ChallengeContribution
import club.staircrusher.challenge.domain.model.ChallengeParticipation
import club.staircrusher.challenge.domain.model.ChallengeStatus
import club.staircrusher.challenge.domain.model.ChallengeRank
import club.staircrusher.challenge.domain.model.ChallengeStatus
import club.staircrusher.infra.persistence.sqldelight.migration.Challenge
import club.staircrusher.infra.persistence.sqldelight.migration.Challenge_contribution
import club.staircrusher.infra.persistence.sqldelight.migration.Challenge_participation
import club.staircrusher.infra.persistence.sqldelight.migration.Challenge_rank
import club.staircrusher.infra.persistence.sqldelight.query.challenge.FindByUidAndTime
import club.staircrusher.infra.persistence.sqldelight.query.challenge.JoinedChallenges
import club.staircrusher.infra.persistence.sqldelight.query.challenge.NotJoinedChallenges
import club.staircrusher.stdlib.time.toOffsetDateTime
import java.time.Instant

Expand Down Expand Up @@ -161,3 +160,9 @@ fun Challenge_rank.toDomainModel() = ChallengeRank(
createdAt = created_at.toInstant(),
updatedAt = updated_at.toInstant()
)

fun ChallengeRank.toDto(nickname: String) = ChallengeRankDto(
contributionCount = contributionCount,
rank = rank,
nickname = nickname,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ CREATE TABLE IF NOT EXISTS challenge_rank (
id VARCHAR(36) NOT NULL,
challenge_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
contribution_count INT NOT NULL,
contribution_count BIGINT NOT NULL,
rank BIGINT NOT NULL,
created_at TIMESTAMP(6) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(6) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand Down

0 comments on commit d1df5f9

Please sign in to comment.