Feat/#11 미션 완료 응답에 트랙 완료 여부 추가 및 다음 트랙 전환 API 구현 - #13
Conversation
📝 WalkthroughWalkthroughMission completion now returns track completion details, evaluates daily requirements by track type, and advances eligible users through sequential tracks. A new endpoint explicitly proceeds to the next track, while mission selection and progress calculations use the revised completion rules. ChangesMission Track Progression
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant MissionController
participant MissionService
participant TrackRepository
Client->>MissionController: POST /api/missions/proceed
MissionController->>MissionService: proceedToNextTrack(user)
MissionService->>MissionService: validate completed days
MissionService->>TrackRepository: find next track
MissionService-->>MissionController: MissionCompleteResponse
MissionController-->>Client: API response
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java (1)
98-101: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winDuplicated + N+1 day-completion counting.
This
allDates.stream().filter(isDayCompleted).count()block is repeated verbatim inproceedToNextTrack(Line 195-197) andgetMissionProgress(Line 225-227), and eachisDayCompletedcall issues 2–3 additional queries per date. Extracting acountCompletedDays(user, track, joinedAt)helper removes the duplication and gives one place to optimize the per-date query fan-out (e.g., pre-fetching logs/missions once).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java` around lines 98 - 101, Extract the repeated completed-day calculation into a private countCompletedDays(user, track, joinedAt) helper and replace the duplicated stream blocks in proceedToNextTrack and getMissionProgress with calls to it. Preserve the existing isDayCompleted behavior while centralizing the logic, and structure the helper as the single location for future optimization of per-date query fan-out.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/team4/hackerton/domain/mission/controller/MissionController.java`:
- Around line 54-68: Update MissionService.completeMission so track advancement
is handled consistently with the documented API contract, then revise
MissionController.completeMission’s Swagger description to match the actual
service behavior and remove the incorrect instruction to call POST
/api/missions/proceed when trackCompleted is true.
In
`@src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java`:
- Around line 107-119: Update completeMission to stop advancing the track:
remove its next-track lookup and UserTrack creation, while retaining only the
completion reporting fields (trackCompleted and nextTrackName). Keep the
transition logic exclusively in proceedToNextTrack, which already completes the
current track and creates the next UserTrack.
- Around line 133-142: Update isDayCompleted so non-SELF_CARE progression
requires the scheduled common mission but only checks for a completed
USER_CUSTOM mission when custom missions exist; return true after the common
mission for users with no custom missions, while preserving the existing
custom-completion requirement when applicable.
---
Nitpick comments:
In
`@src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java`:
- Around line 98-101: Extract the repeated completed-day calculation into a
private countCompletedDays(user, track, joinedAt) helper and replace the
duplicated stream blocks in proceedToNextTrack and getMissionProgress with calls
to it. Preserve the existing isDayCompleted behavior while centralizing the
logic, and structure the helper as the single location for future optimization
of per-date query fan-out.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 02dbd3b7-a3a8-4e26-9d63-0f1c3c8edb4b
📒 Files selected for processing (7)
src/main/java/com/team4/hackerton/domain/mission/code/MissionErrorCode.javasrc/main/java/com/team4/hackerton/domain/mission/code/MissionSuccessCode.javasrc/main/java/com/team4/hackerton/domain/mission/controller/MissionController.javasrc/main/java/com/team4/hackerton/domain/mission/dto/response/MissionCompleteResponse.javasrc/main/java/com/team4/hackerton/domain/mission/repository/MissionRepository.javasrc/main/java/com/team4/hackerton/domain/mission/service/MissionService.javasrc/main/java/com/team4/hackerton/domain/track/entity/TrackType.java
| description = "특정 미션을 오늘 완료 처리합니다. 하루에 같은 미션은 1번만 완료할 수 있습니다.\n\n" | ||
| + "- 응답의 `trackCompleted`가 `true`이면 `POST /api/missions/proceed`를 호출하여 다음 트랙으로 이동하세요." | ||
| ) | ||
| @ApiResponses({ | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "미션 완료 성공"), | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "미션을 찾을 수 없음 (MISSION404_2)", content = @Content(schema = @Schema(hidden = true))), | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "이미 완료한 미션 (MISSION409_1)", content = @Content(schema = @Schema(hidden = true))) | ||
| }) | ||
| @PostMapping("/{missionId}/complete") | ||
| public ApiResponse<Void> completeMission( | ||
| public ApiResponse<MissionCompleteResponse> completeMission( | ||
| @AuthenticationPrincipal CustomUserDetails userDetails, | ||
| @PathVariable Long missionId) { | ||
| missionService.completeMission(userDetails.getUser(), missionId); | ||
| return ApiResponse.onSuccess(MissionSuccessCode.COMPLETE_MISSION_SUCCESS); | ||
| MissionCompleteResponse response = missionService.completeMission(userDetails.getUser(), missionId); | ||
| return ApiResponse.onSuccess(MissionSuccessCode.COMPLETE_MISSION_SUCCESS, response); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
The doc instructs clients to call POST /api/missions/proceed when trackCompleted is true, but the service already advances the track inside completeMission. This is the downstream symptom of the service-layer issue flagged in MissionService.completeMission (Line 107-119); resolving it there keeps this contract consistent.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/team4/hackerton/domain/mission/controller/MissionController.java`
around lines 54 - 68, Update MissionService.completeMission so track advancement
is handled consistently with the documented API contract, then revise
MissionController.completeMission’s Swagger description to match the actual
service behavior and remove the incorrect instruction to call POST
/api/missions/proceed when trackCompleted is true.
| userTrack.complete(today); | ||
|
|
||
| TrackType nextType = track.getTrackType().next(); | ||
| if (nextType == null) { | ||
| return new MissionCompleteResponse(true, null); | ||
| } | ||
|
|
||
| Track nextTrack = trackRepository.findByTrackType(nextType) | ||
| .orElseThrow(() -> new AppException(TrackErrorCode.TRACK_NOT_FOUND)); | ||
| userTrackRepository.save(new UserTrack(user, nextTrack, today)); | ||
|
|
||
| return new MissionCompleteResponse(true, nextType.getDisplayName()); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the service and controller to inspect the relevant methods and docs.
ast-grep outline src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java --view expanded
printf '\n---\n'
ast-grep outline src/main/java/com/team4/hackerton/domain/mission/controller/MissionController.java --view expanded
printf '\n---\n'
ast-grep outline src/main/java/com/team4/hackerton/domain/mission/dto/MissionCompleteResponse.java --view expanded
printf '\n---\n'
ast-grep outline src/main/java/com/team4/hackerton/domain/mission/dto --view expandedRepository: 8th-COKERTHON/server-team4
Length of output: 1598
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read only the relevant slices after locating line numbers.
sed -n '1,260p' src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java
printf '\n---CONTROLLER---\n'
sed -n '1,260p' src/main/java/com/team4/hackerton/domain/mission/controller/MissionController.javaRepository: 8th-COKERTHON/server-team4
Length of output: 18642
completeMission should not advance the track here. proceedToNextTrack already calls userTrack.complete(today) and creates the next UserTrack, so this path is mutating the same state twice and makes the /complete → /proceed flow inconsistent. Keep completeMission to reporting trackCompleted/nextTrackName only, and leave the transition to proceedToNextTrack.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java`
around lines 107 - 119, Update completeMission to stop advancing the track:
remove its next-track lookup and UserTrack creation, while retaining only the
completion reporting fields (trackCompleted and nextTrackName). Keep the
transition logic exclusively in proceedToNextTrack, which already completes the
current track and creates the next UserTrack.
| } else { | ||
| if (defaultMissions.isEmpty()) return false; | ||
| long dayIndex = ChronoUnit.DAYS.between(joinedAt, date); | ||
| Mission todayCommon = defaultMissions.get((int) (dayIndex % defaultMissions.size())); | ||
| if (!completedMissionIds.contains(todayCommon.getId())) return false; | ||
|
|
||
| Set<Long> customMissionIds = missionRepository.findByUserAndTrackAndType(user, track, MissionType.USER_CUSTOM) | ||
| .stream().map(Mission::getId).collect(Collectors.toSet()); | ||
| return completedMissionIds.stream().anyMatch(customMissionIds::contains); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file around the reported lines
sed -n '1,240p' src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java
# Search for related mission type logic and custom mission limits
rg -n "SELF_CARE|USER_CUSTOM|isDayCompleted|defaultMissions|completedMissionIds|custom mission|customMissions|missionRepository.findByUserAndTrackAndType" src/main/javaRepository: 8th-COKERTHON/server-team4
Length of output: 16602
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,240p' src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java
printf '\n--- SEARCH ---\n'
rg -n "SELF_CARE|USER_CUSTOM|isDayCompleted|defaultMissions|completedMissionIds|custom mission|customMissions|missionRepository.findByUserAndTrackAndType" src/main/javaRepository: 8th-COKERTHON/server-team4
Length of output: 16618
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map mission-related files and inspect the service and entity definitions.
git ls-files | rg '^src/main/java/.+Mission|^src/main/java/.+Track|^src/main/java/.+MissionType|^src/main/java/.+MissionRepository|^src/main/java/.+TrackRepository'
printf '\n--- MissionService outline ---\n'
ast-grep outline src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java --view expanded || true
printf '\n--- Search for custom-mission constraints ---\n'
rg -n "0-2|2개|USER_CUSTOM|SELF_CARE|create.*custom|add.*custom|findByUserAndTrackAndType|isDayCompleted|completedMissionIds" src/main/javaRepository: 8th-COKERTHON/server-team4
Length of output: 7715
Non-SELF_CARE progression is gated on an optional custom mission.
isDayCompleted requires a completed USER_CUSTOM mission after the common mission, but custom missions are optional and capped at 2. A user with no custom mission can never count a day as complete, so track progress gets stuck.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/team4/hackerton/domain/mission/service/MissionService.java`
around lines 133 - 142, Update isDayCompleted so non-SELF_CARE progression
requires the scheduled common mission but only checks for a completed
USER_CUSTOM mission when custom missions exist; return true after the common
mission for users with no custom missions, while preserving the existing
custom-completion requirement when applicable.
Summary by CodeRabbit
New Features
Bug Fixes