Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import com.zerost.api.common.exception.ErrorCode
import com.zerost.api.ecojam.domain.EcoJamHistory
import com.zerost.api.ecojam.domain.EcoJamHistoryRepository
import com.zerost.api.ecojam.domain.EcoJamHistorySourceType
import com.zerost.api.point.domain.PointHistory
import com.zerost.api.point.domain.PointHistoryRepository
import com.zerost.api.point.application.PointAwardService
import com.zerost.api.point.domain.PointHistorySourceType
import com.zerost.api.user.domain.UserRepository
import org.slf4j.LoggerFactory
Expand All @@ -22,7 +21,7 @@ import org.springframework.transaction.annotation.Transactional
class AdminAssetGrantService(
private val userRepository: UserRepository,
private val ecoJamHistoryRepository: EcoJamHistoryRepository,
private val pointHistoryRepository: PointHistoryRepository,
private val pointAwardService: PointAwardService,
) {

@Transactional(readOnly = true)
Expand Down Expand Up @@ -62,7 +61,7 @@ class AdminAssetGrantService(
val user = userRepository.findByIdForUpdate(userId)
.orElseThrow { BusinessException(ErrorCode.USER_NOT_FOUND) }

when (request.assetType) {
val actualGrantedAmount = when (request.assetType) {
AdminAssetType.ECO_JAM -> {
user.increaseEcoJam(grantedAmount)
ecoJamHistoryRepository.save(
Expand All @@ -78,25 +77,24 @@ class AdminAssetGrantService(
grantedAmount = grantedAmount,
balanceAfter = user.ecoJam,
)
grantedAmount
}
AdminAssetType.POINT -> {
user.increasePoint(grantedAmount)
pointHistoryRepository.save(
PointHistory.earn(
user = user,
amount = grantedAmount,
sourceType = PointHistorySourceType.ADMIN_GRANT,
sourceId = adminUserId,
),
val awardedPoint = pointAwardService.award(
user = user,
requestedAmount = grantedAmount,
sourceType = PointHistorySourceType.ADMIN_GRANT,
sourceId = adminUserId,
)
results += AdminAssetGrantItemResult(
userId = userId,
grantedAmount = grantedAmount,
grantedAmount = awardedPoint,
balanceAfter = user.point,
)
awardedPoint
}
}
totalGrantedAmount += grantedAmount
totalGrantedAmount += actualGrantedAmount
}

log.info(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import com.zerost.api.ingredient.domain.IngredientRepository
import com.zerost.api.ingredient.domain.IngredientHistorySourceType
import com.zerost.api.ingredient.domain.UserIngredient
import com.zerost.api.ingredient.domain.UserIngredientRepository
import com.zerost.api.point.domain.PointHistory
import com.zerost.api.point.domain.PointHistoryRepository
import com.zerost.api.point.application.PointAwardService
import com.zerost.api.point.domain.PointHistorySourceType
import com.zerost.api.user.domain.User
import com.zerost.api.user.domain.UserRepository
Expand All @@ -38,7 +37,7 @@ class GachaExecutionService(
private val userIngredientRepository: UserIngredientRepository,
private val ingredientHistoryRepository: IngredientHistoryRepository,
private val ecoJamHistoryRepository: EcoJamHistoryRepository,
private val pointHistoryRepository: PointHistoryRepository,
private val pointAwardService: PointAwardService,
private val gachaRandomProvider: GachaRandomProvider,
) {

Expand Down Expand Up @@ -159,7 +158,12 @@ class GachaExecutionService(
gachaId: Long,
) {
if (gacha.resultPoint > 0) {
user.increasePoint(gacha.resultPoint)
gacha.resultPoint = pointAwardService.award(
user = user,
requestedAmount = gacha.resultPoint,
sourceType = PointHistorySourceType.GACHA,
sourceId = gachaId,
)
}

if (gacha.resultEcoJam > 0) {
Expand Down Expand Up @@ -216,17 +220,6 @@ class GachaExecutionService(
)
)
}

if (gacha.resultPoint > 0) {
pointHistoryRepository.save(
PointHistory.earn(
user = user,
amount = gacha.resultPoint,
sourceType = PointHistorySourceType.GACHA,
sourceId = gachaId,
)
)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.zerost.api.point.application

import com.zerost.api.common.exception.BusinessException
import com.zerost.api.common.exception.ErrorCode
import com.zerost.api.point.domain.PointHistory
import com.zerost.api.point.domain.PointHistoryRepository
import com.zerost.api.point.domain.PointHistorySourceType
import com.zerost.api.user.domain.User
import com.zerost.api.user.domain.UserRepository
import org.springframework.stereotype.Service

@Service
class PointAwardService(
private val userRepository: UserRepository,
private val pointHistoryRepository: PointHistoryRepository,
private val pointPolicyProperties: PointPolicyProperties,
) {
private val maxCumulativeEarnAmountPerUser: Int? = pointPolicyProperties.resolvedMaxCumulativeEarnAmountPerUser()

fun award(
user: User,
requestedAmount: Int,
sourceType: PointHistorySourceType,
sourceId: Long,
): Int {
require(requestedAmount >= 0) { "์ง€๊ธ‰ ์š”์ฒญ ํฌ์ธํŠธ๋Š” 0 ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." }

if (requestedAmount == 0) {
return 0
}

val lockedUser = userRepository.findByIdForUpdate(requireNotNull(user.id))
.orElseThrow { BusinessException(ErrorCode.USER_NOT_FOUND) }
val grantedAmount = resolveGrantedAmount(requireNotNull(lockedUser.id), requestedAmount)
if (grantedAmount == 0) {
return 0
}

lockedUser.increasePoint(grantedAmount)
pointHistoryRepository.save(
PointHistory.earn(
user = lockedUser,
amount = grantedAmount,
sourceType = sourceType,
sourceId = sourceId,
),
)

return grantedAmount
}

private fun resolveGrantedAmount(
userId: Long,
requestedAmount: Int,
): Int {
val maxCumulativeEarnAmountPerUser = maxCumulativeEarnAmountPerUser
?: return requestedAmount

val currentEarnedAmount = pointHistoryRepository.sumEarnedAmountByUserId(userId)
val remainingAmount = (maxCumulativeEarnAmountPerUser.toLong() - currentEarnedAmount).coerceAtLeast(0L)

return requestedAmount.toLong()
.coerceAtMost(remainingAmount)
.toInt()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.zerost.api.point.application

import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration

@Configuration
@EnableConfigurationProperties(PointPolicyProperties::class)
class PointPolicyConfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.zerost.api.point.application

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "app.point.policy")
data class PointPolicyProperties(
val maxCumulativeEarnAmountPerUser: String? = null,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
) {

init {
resolvedMaxCumulativeEarnAmountPerUser()
}

fun resolvedMaxCumulativeEarnAmountPerUser(): Int? {
val rawValue = maxCumulativeEarnAmountPerUser
?.trim()
?.takeIf { it.isNotEmpty() }
?: return null

val parsedValue = rawValue.toIntOrNull()
?: throw IllegalArgumentException("ํฌ์ธํŠธ ๋ˆ„์  ์ง€๊ธ‰ ์ƒํ•œ ์„ค์ •๊ฐ’์€ 0 ์ด์ƒ์˜ ์ •์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. value=$rawValue")

require(parsedValue >= 0) {
"ํฌ์ธํŠธ ๋ˆ„์  ์ง€๊ธ‰ ์ƒํ•œ ์„ค์ •๊ฐ’์€ 0 ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. value=$rawValue"
}

return parsedValue
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package com.zerost.api.point.domain

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

Comment thread
coderabbitai[bot] marked this conversation as resolved.
interface PointHistoryRepository : JpaRepository<PointHistory, Long> {
fun findAllByUserIdOrderByCreatedAtDescIdDesc(userId: Long): List<PointHistory>

@Query(
"""
select coalesce(sum(ph.amount), 0)
from PointHistory ph
where ph.user.id = :userId
and ph.amount > 0
""",
)
fun sumEarnedAmountByUserId(@Param("userId") userId: Long): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.zerost.api.ingredient.domain.IngredientRepository
import com.zerost.api.ingredient.domain.IngredientType
import com.zerost.api.ingredient.domain.UserIngredient
import com.zerost.api.ingredient.domain.UserIngredientRepository
import com.zerost.api.point.application.PointAwardService
import com.zerost.api.point.domain.PointHistory
import com.zerost.api.point.domain.PointHistoryRepository
import com.zerost.api.point.domain.PointHistorySourceType
Expand Down Expand Up @@ -52,6 +53,7 @@ class SoupRewardService(
private val ingredientHistoryRepository: IngredientHistoryRepository,
private val ecoJamHistoryRepository: EcoJamHistoryRepository,
private val pointHistoryRepository: PointHistoryRepository,
private val pointAwardService: PointAwardService,
private val soupRewardPolicyRepository: SoupRewardPolicyRepository,
private val soupRewardPolicyIngredientRepository: SoupRewardPolicyIngredientRepository,
private val soupBonusRewardPolicyRepository: SoupBonusRewardPolicyRepository,
Expand Down Expand Up @@ -79,19 +81,11 @@ class SoupRewardService(
bonusReward = bonusReward,
)

return SoupRewardSummary(
rewardGrade = reward.rewardGrade.name,
ecoJam = reward.ecoJam,
point = reward.point,
rewardedIngredients = reward.rewardedIngredients.map { ingredientReward ->
SoupRewardIngredientResponse(
ingredientId = requireNotNull(ingredientReward.ingredient.id),
ingredientName = ingredientReward.ingredient.name,
quantity = ingredientReward.quantity,
)
},
baseReward = baseReward.toSectionResponse(),
bonusReward = bonusReward?.toSectionResponse(),
return buildRewardSummary(
soup = soup,
rewardedIngredients = reward.rewardedIngredients,
baseReward = baseReward.copy(point = soup.baseRewardPoint),
bonusReward = bonusReward?.copy(point = soup.bonusRewardPoint),
)
}

Expand All @@ -110,18 +104,11 @@ class SoupRewardService(
ingredientHistorySourceType = IngredientHistorySourceType.SOUP_REROLL,
)

return SoupRewardSummary(
rewardGrade = reward.rewardGrade.name,
ecoJam = reward.ecoJam,
point = reward.point,
rewardedIngredients = reward.rewardedIngredients.map { ingredientReward ->
SoupRewardIngredientResponse(
ingredientId = requireNotNull(ingredientReward.ingredient.id),
ingredientName = ingredientReward.ingredient.name,
quantity = ingredientReward.quantity,
)
},
baseReward = reward.toSectionResponse(),
return buildRewardSummary(
soup = soup,
rewardedIngredients = reward.rewardedIngredients,
baseReward = reward.copy(point = soup.baseRewardPoint),
bonusReward = null,
)
}

Expand Down Expand Up @@ -237,17 +224,31 @@ class SoupRewardService(
) {
soup.rewardGrade = reward.rewardGrade
soup.rewardEcoJam = reward.ecoJam
soup.rewardPoint = reward.point
soup.rewardPoint = 0
soup.baseRewardGrade = baseReward.rewardGrade
soup.baseRewardEcoJam = baseReward.ecoJam
soup.baseRewardPoint = baseReward.point
soup.baseRewardPoint = 0
soup.bonusRewardGrade = bonusReward?.rewardGrade
soup.bonusRewardEcoJam = bonusReward?.ecoJam ?: 0
soup.bonusRewardPoint = bonusReward?.point ?: 0
soup.bonusRewardPoint = 0

soup.user.increaseEcoJam(reward.ecoJam)
soup.user.increasePoint(reward.point)
saveHistories(soup, reward, ecoJamHistorySourceType, pointHistorySourceType)
val grantedPoint = pointAwardService.award(
user = soup.user,
requestedAmount = reward.point,
sourceType = pointHistorySourceType,
sourceId = requireNotNull(soup.id),
)
val adjustedBaseRewardPoint = baseReward.point.coerceAtMost(grantedPoint)
val adjustedBonusRewardPoint = bonusReward?.point?.let { bonusPoint ->
(grantedPoint - adjustedBaseRewardPoint).coerceAtLeast(0).coerceAtMost(bonusPoint)
} ?: 0

soup.rewardPoint = grantedPoint
soup.baseRewardPoint = adjustedBaseRewardPoint
soup.bonusRewardPoint = adjustedBonusRewardPoint

saveHistories(soup, reward.copy(point = grantedPoint), ecoJamHistorySourceType)

reward.rewardedIngredients.forEach { ingredientReward ->
val userIngredient = userIngredientRepository.findByUserAndIngredient(soup.user, ingredientReward.ingredient)
Expand Down Expand Up @@ -331,7 +332,6 @@ class SoupRewardService(
soup: Soup,
reward: RewardResult,
ecoJamHistorySourceType: EcoJamHistorySourceType,
pointHistorySourceType: PointHistorySourceType,
) {
val soupId = requireNotNull(soup.id)

Expand All @@ -345,17 +345,28 @@ class SoupRewardService(
),
)
}
}

if (reward.point > 0) {
pointHistoryRepository.save(
PointHistory.earn(
user = soup.user,
amount = reward.point,
sourceType = pointHistorySourceType,
sourceId = soupId,
),
)
}
private fun buildRewardSummary(
soup: Soup,
rewardedIngredients: List<IngredientReward>,
baseReward: RewardResult,
bonusReward: RewardResult?,
): SoupRewardSummary {
return SoupRewardSummary(
rewardGrade = soup.rewardGrade.name,
ecoJam = soup.rewardEcoJam,
point = soup.rewardPoint,
rewardedIngredients = rewardedIngredients.map { ingredientReward ->
SoupRewardIngredientResponse(
ingredientId = requireNotNull(ingredientReward.ingredient.id),
ingredientName = ingredientReward.ingredient.name,
quantity = ingredientReward.quantity,
)
},
baseReward = baseReward.toSectionResponse(),
bonusReward = bonusReward?.toSectionResponse(),
)
}

private fun randomCommonIngredient(): Ingredient = ingredientRepository.findAllByType(IngredientType.COMMON)
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ app:
allowed-origins: ${APP_CORS_ALLOWED_ORIGINS:}
public-assets:
base-url: ${APP_PUBLIC_ASSETS_BASE_URL:https://assets.zero-st.com}
point:
policy:
max-cumulative-earn-amount-per-user: ${APP_POINT_POLICY_MAX_CUMULATIVE_EARN_AMOUNT_PER_USER:}
storage:
s3:
bucket: ${APP_STORAGE_S3_BUCKET}
Expand Down
Loading
Loading