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 @@ -106,20 +106,24 @@ class MissionProgressService(
userLevel.levelUp()
userLevelRepository.save(userLevel)

val nextLevel = userLevel.currentLevel
val nextMission = missionRepository.findByLevel(nextLevel)
val nextMissionLevel = userLevel.currentLevel + 1
if (nextMissionLevel > 10) {
logger.info("모든 미션 완료 - userId: $userId, currentLevel: ${userLevel.currentLevel}")
return
}

val nextMission = missionRepository.findByLevel(nextMissionLevel)
if (nextMission != null) {
val nextUserMission = UserMission.create(userId, nextMission.id)
nextUserMission.levelUpPopupShown = false
userMissionRepository.save(nextUserMission)

logger.info(
"레벨업 및 다음 미션 생성 - userId: $userId, " +
"currentLevel: $nextLevel, missionType: ${nextMission.missionType}",
"currentLevel: ${userLevel.currentLevel}, missionType: ${nextMission.missionType}",
)
} else {
logger.warn("Lv.$nextLevel 미션 정의가 없습니다")
logger.warn("Lv.$nextMissionLevel 미션 정의가 없습니다")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ class MissionQueryService(
val userLevel = userLevelRepository.findByUserId(userId) ?: throw UserLevelNotFoundException(userId)
val userMission =
userMissionRepository.findCurrentMissionByUserId(userId) ?: throw CurrentMissionNotFoundException(userId)
val missionLevel = userLevel.currentLevel + 1
val mission =
missionRepository.findByLevel(userLevel.currentLevel) ?: throw MissionDefinitionNotFoundException(
userLevel.currentLevel,
)
missionRepository.findByLevel(missionLevel) ?: throw MissionDefinitionNotFoundException(missionLevel)

val missionInfo =
MissionInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ class UserLevelQueryService(
override fun getCurrentLevel(userId: UUID): Int = userLevelRepository.findByUserId(userId)?.currentLevel ?: DEFAULT_LEVEL

companion object {
private const val DEFAULT_LEVEL = 1
private const val DEFAULT_LEVEL = 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class AdminAchievementStatsService(
private val userRepository: UserRepository,
) : AdminAchievementStatsFinder {
companion object {
private const val MIN_LEVEL = 1
private const val MIN_LEVEL = 0
private const val MAX_LEVEL = 10
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/didit/domain/achievement/UserLevel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class UserLevel(
@Column(columnDefinition = "BINARY(16)")
val userId: UUID,
@Column(nullable = false)
var currentLevel: Int = 1,
var currentLevel: Int = 0,
@Column(nullable = false)
var updatedAt: LocalDateTime = LocalDateTime.now(),
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- 레벨을 0부터 시작하도록 변경: currentLevel = 완료한 미션 수(0~10).
-- 기존 1-base(시작=1) 데이터를 0-base로 한 칸 내리고, 기본값을 0으로 변경한다.
ALTER TABLE user_levels MODIFY current_level INT NOT NULL DEFAULT 0;

UPDATE user_levels SET current_level = current_level - 1 WHERE current_level > 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve users who already completed Lv10

For production data where a user has already completed the level-10 mission, the old code left user_levels.current_level at 10 because awardNextLevel() returned when currentLevel >= 10; this migration unconditionally rewrites that row to 9. Since the new meaning is “completed mission count,” those maxed-out users should remain at 10, otherwise profiles/admin stats permanently demote them until some separate repair runs. Consider special-casing rows with a completed user_missions entry joined to missions.level = 10 before applying the blanket - 1 conversion.

Useful? React with 👍 / 👎.

Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class MissionQueryServiceTest {

@Test
fun `Lv1 FIRST_RETRO 미션을 조회한다`() {
val userLevel = UserLevel(userId = userId, currentLevel = 1)
val userLevel = UserLevel(userId = userId, currentLevel = 0)
val mission = Mission.firstRetro()
val userMission =
UserMission(
Expand All @@ -73,7 +73,7 @@ class MissionQueryServiceTest {

val result = missionQueryService.getCurrentMission(userId)

assertThat(result.currentLevel).isEqualTo(1)
assertThat(result.currentLevel).isEqualTo(0)
assertThat(result.mission.type).isEqualTo("FIRST_RETRO")
assertThat(result.mission.progress).isEqualTo(0)
assertThat(result.mission.target).isEqualTo(1)
Expand All @@ -84,7 +84,7 @@ class MissionQueryServiceTest {

@Test
fun `Lv2 TIME_LIMITED 미션의 남은 일수를 포함한다`() {
val userLevel = UserLevel(userId = userId, currentLevel = 2)
val userLevel = UserLevel(userId = userId, currentLevel = 1)
val mission = Mission.timeLimited()
val startedAt = LocalDateTime.now().minusDays(2)
val userMission =
Expand All @@ -102,15 +102,15 @@ class MissionQueryServiceTest {

val result = missionQueryService.getCurrentMission(userId)

assertThat(result.currentLevel).isEqualTo(2)
assertThat(result.currentLevel).isEqualTo(1)
assertThat(result.mission.type).isEqualTo("TIME_LIMITED")
assertThat(result.mission.remainingDays).isEqualTo(5)
assertThat(result.weeklyStatus).isNull()
}

@Test
fun `Lv3 CONSECUTIVE_WEEK 미션의 주간 회고 현황을 포함한다`() {
val userLevel = UserLevel(userId = userId, currentLevel = 3)
val userLevel = UserLevel(userId = userId, currentLevel = 2)
val mission = Mission.consecutiveWeek(level = 3, weeks = 2)
val userMission =
UserMission(
Expand All @@ -129,7 +129,7 @@ class MissionQueryServiceTest {

val result = missionQueryService.getCurrentMission(userId)

assertThat(result.currentLevel).isEqualTo(3)
assertThat(result.currentLevel).isEqualTo(2)
assertThat(result.mission.type).isEqualTo("CONSECUTIVE_WEEK")
assertThat(result.weeklyStatus).isNotNull()
assertThat(result.weeklyStatus!!.days).hasSize(7)
Expand All @@ -155,7 +155,7 @@ class MissionQueryServiceTest {

@Test
fun `미션 정의가 없으면 MissionDefinitionNotFoundException을 발생시킨다`() {
val userLevel = UserLevel(userId = userId, currentLevel = 1)
val userLevel = UserLevel(userId = userId, currentLevel = 0)
val userMission = UserMission(userId = userId, missionId = UUID.randomUUID())

whenever(userLevelRepository.findByUserId(userId)).thenReturn(userLevel)
Expand All @@ -168,7 +168,7 @@ class MissionQueryServiceTest {

@Test
fun `레벨업 팝업을 반환한다`() {
val userLevel = UserLevel(userId = userId, currentLevel = 1)
val userLevel = UserLevel(userId = userId, currentLevel = 0)
val mission = Mission.firstRetro()
val userMission =
UserMission(
Expand All @@ -191,7 +191,7 @@ class MissionQueryServiceTest {

@Test
fun `실패 팝업을 반환한다`() {
val userLevel = UserLevel(userId = userId, currentLevel = 1)
val userLevel = UserLevel(userId = userId, currentLevel = 0)
val mission = Mission.firstRetro()
val userMission =
UserMission(
Expand All @@ -214,7 +214,7 @@ class MissionQueryServiceTest {

@Test
fun `팝업이 없으면 exists가 false이다`() {
val userLevel = UserLevel(userId = userId, currentLevel = 1)
val userLevel = UserLevel(userId = userId, currentLevel = 0)
val mission = Mission.firstRetro()
val userMission =
UserMission(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,17 @@ class AdminAchievementStatsServiceTest {
}

@Test
fun `레벨 분포는 1부터 10까지 채우고, UserLevel 행이 없는 유저는 레벨 1로 집계한다`() {
fun `레벨 분포는 0부터 10까지 채우고, UserLevel 행이 없는 유저는 레벨 0으로 집계한다`() {
whenever(userLevelRepository.countGroupByLevel()).thenReturn(
listOf(levelCount(1, 5), levelCount(3, 2)),
)
whenever(userRepository.countByDeletedAtIsNull()).thenReturn(10)

val result = service().getLevelStats()

assertThat(result).hasSize(10)
assertThat(result.first { it.level == 1 }.userCount).isEqualTo(8)
assertThat(result).hasSize(11)
assertThat(result.first { it.level == 0 }.userCount).isEqualTo(3)
assertThat(result.first { it.level == 1 }.userCount).isEqualTo(5)
assertThat(result.first { it.level == 2 }.userCount).isEqualTo(0)
assertThat(result.first { it.level == 3 }.userCount).isEqualTo(2)
}
Expand Down
Loading