diff --git a/src/main/kotlin/com/didit/application/achievement/MissionProgressService.kt b/src/main/kotlin/com/didit/application/achievement/MissionProgressService.kt index 4e9254ae..b824fd66 100644 --- a/src/main/kotlin/com/didit/application/achievement/MissionProgressService.kt +++ b/src/main/kotlin/com/didit/application/achievement/MissionProgressService.kt @@ -106,9 +106,13 @@ 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 @@ -116,10 +120,10 @@ class MissionProgressService( logger.info( "레벨업 및 다음 미션 생성 - userId: $userId, " + - "currentLevel: $nextLevel, missionType: ${nextMission.missionType}", + "currentLevel: ${userLevel.currentLevel}, missionType: ${nextMission.missionType}", ) } else { - logger.warn("Lv.$nextLevel 미션 정의가 없습니다") + logger.warn("Lv.$nextMissionLevel 미션 정의가 없습니다") } } } diff --git a/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt b/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt index f7189df2..8556caf4 100644 --- a/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt +++ b/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt @@ -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( diff --git a/src/main/kotlin/com/didit/application/achievement/UserLevelQueryService.kt b/src/main/kotlin/com/didit/application/achievement/UserLevelQueryService.kt index 26c950ff..cfb25725 100644 --- a/src/main/kotlin/com/didit/application/achievement/UserLevelQueryService.kt +++ b/src/main/kotlin/com/didit/application/achievement/UserLevelQueryService.kt @@ -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 } } diff --git a/src/main/kotlin/com/didit/application/admin/AdminAchievementStatsService.kt b/src/main/kotlin/com/didit/application/admin/AdminAchievementStatsService.kt index adb71ceb..bde80d3b 100644 --- a/src/main/kotlin/com/didit/application/admin/AdminAchievementStatsService.kt +++ b/src/main/kotlin/com/didit/application/admin/AdminAchievementStatsService.kt @@ -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 } diff --git a/src/main/kotlin/com/didit/domain/achievement/UserLevel.kt b/src/main/kotlin/com/didit/domain/achievement/UserLevel.kt index 331aeb88..6e57616d 100644 --- a/src/main/kotlin/com/didit/domain/achievement/UserLevel.kt +++ b/src/main/kotlin/com/didit/domain/achievement/UserLevel.kt @@ -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(), ) { diff --git a/src/main/resources/db/migration/V43__user_level_zero_based.sql b/src/main/resources/db/migration/V43__user_level_zero_based.sql new file mode 100644 index 00000000..31780acb --- /dev/null +++ b/src/main/resources/db/migration/V43__user_level_zero_based.sql @@ -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; diff --git a/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt b/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt index 6d7dba8c..2866ede5 100644 --- a/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt +++ b/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt @@ -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( @@ -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) @@ -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 = @@ -102,7 +102,7 @@ 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() @@ -110,7 +110,7 @@ class MissionQueryServiceTest { @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( @@ -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) @@ -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) @@ -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( @@ -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( @@ -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( diff --git a/src/test/kotlin/com/didit/application/admin/AdminAchievementStatsServiceTest.kt b/src/test/kotlin/com/didit/application/admin/AdminAchievementStatsServiceTest.kt index 1a4b58ed..d681d895 100644 --- a/src/test/kotlin/com/didit/application/admin/AdminAchievementStatsServiceTest.kt +++ b/src/test/kotlin/com/didit/application/admin/AdminAchievementStatsServiceTest.kt @@ -69,7 +69,7 @@ class AdminAchievementStatsServiceTest { } @Test - fun `레벨 분포는 1부터 10까지 채우고, UserLevel 행이 없는 유저는 레벨 1로 집계한다`() { + fun `레벨 분포는 0부터 10까지 채우고, UserLevel 행이 없는 유저는 레벨 0으로 집계한다`() { whenever(userLevelRepository.countGroupByLevel()).thenReturn( listOf(levelCount(1, 5), levelCount(3, 2)), ) @@ -77,8 +77,9 @@ class AdminAchievementStatsServiceTest { 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) }