feat: 어드민 학기 생성 API 구현 - #2324
Conversation
📝 WalkthroughWalkthroughAdds ChangesTimetable semester creation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/in/koreatech/koin/admin/semester/dto/AdminTimetableSemesterCreateRequest.java`:
- Around line 10-18: Update the year validation in
AdminTimetableSemesterCreateRequest to reject values whose generated semester
labels exceed the 10-character field, including 10,000,000; use the existing
validation mechanism and ensure the created label is stored back into Semester
so DTO validation remains the user-visible failure point.
In
`@src/main/java/in/koreatech/koin/admin/semester/service/AdminTimetableSemesterService.java`:
- Around line 20-31: Update createSemester and its transaction handling to catch
the database unique-constraint failure for semester_UNIQUE during save/commit
and translate it to DUPLICATE_SEMESTER, while preserving the existing pre-check.
Add an integration test that runs concurrent identical create requests and
verifies the losing request returns the declared duplicate-semester response.
🪄 Autofix
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: bc7506c9-82de-4bc3-97e8-16bf1a8b9282
📒 Files selected for processing (7)
src/main/java/in/koreatech/koin/admin/history/enums/DomainType.javasrc/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterApi.javasrc/main/java/in/koreatech/koin/admin/semester/controller/AdminSemesterController.javasrc/main/java/in/koreatech/koin/admin/semester/dto/AdminTimetableSemesterCreateRequest.javasrc/main/java/in/koreatech/koin/admin/semester/repository/AdminTimetableSemesterRepository.javasrc/main/java/in/koreatech/koin/admin/semester/service/AdminTimetableSemesterService.javasrc/main/java/in/koreatech/koin/domain/timetable/model/Semester.java
| import jakarta.validation.constraints.NotNull; | ||
| import jakarta.validation.constraints.Positive; | ||
|
|
||
| @JsonNaming(SnakeCaseStrategy.class) | ||
| public record AdminTimetableSemesterCreateRequest( | ||
| @Schema(description = "연도", example = "2026", requiredMode = REQUIRED) | ||
| @NotNull(message = "연도는 필수입니다.") | ||
| @Positive(message = "연도는 양수여야 합니다.") | ||
| Integer year, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
fd -a 'AdminTimetableSemesterCreateRequest.java|Semester.java|.*Semester.*Request.*|.*Semester.*' . | sed 's#^\./##' | head -100
echo
echo "== search relevant symbols =="
rg -n "AdminTimetableSemesterCreateRequest|record AdminTimetableSemester|class AdminTimetableSemester|Semester\.of|`@Size`|max = 10|YEAR|SUMMER|WINTER|semester" src/main/java/in/koreatech/koin -g '*.java' | head -200
echo
echo "== inspect request file =="
F=$(fd -a 'AdminTimetableSemesterCreateRequest.java' . | head -1)
if [ -n "${F:-}" ]; then
cat -n "$F"
fi
echo
echo "== inspect Semester model =="
S=$(fd -a 'Semester.java' . | rg '/domain/timetable/model/Semester.java$' | head -1)
if [ -n "${S:-}" ]; then
cat -n "$S"
fi
echo
echo "== inspect validator/service/controller references =="
rg -n "AdminTimetableSemesterCreateRequest|AdminTimetableSemester|semester|create.*semester|timetable.*semester" src/main/java src/test/java -g '*.java' | head -250Repository: BCSDLab/KOIN_API_V2
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== admin semester service =="
F=$(fd -a 'AdminTimetableSemesterService.java' . | head -1)
if [ -n "${F:-}" ]; then
cat -n "$F"
fi
echo
echo "== admin semester controller relevant lines =="
F=$(fd -a 'AdminSemesterController.java' . | head -1)
if [ -n "${F:-}" ]; then
cat -н "$F" | sed -n '20,90p' | cat -n
fi
echo
echo "== exact tests for admin semester create path =="
rg -n "POST .*/admin/semesters|semesters|AdminTimetableSemesterCreateRequest|AdminSemester.*Test|semester.*year|createSemester|`@Valid`|ConstraintViolation|400" src/test/in/koreatech/koin src/test/java/in/koreatech/koin -g '*.java' | head -300
echo
echo "== deterministic generated label lengths for edge cases =="
python3 - <<'PY'
samples = [1, 9, 10, 99, 100, 999, 1000, 9999, 10000, 9_999_999, 10_000_000]
for year in samples:
for korean in ["여름", "겨울", "1", "2"]:
sem = f"{year}-{korean}" if korean in {"여름", "겨울"} else f"{year}{korean}"
print(f"year={year} term={korean} -> {sem!r} char={len(sem)}", end=" | ")
print()
PY
echo
echo "== repository save method =="
F=$(fd -a 'AdminTimetableSemesterRepository.java' . | head -1)
if [ -n "${F:-}" ]; then
cat -n "$F"
fiRepository: BCSDLab/KOIN_API_V2
Length of output: 1855
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== admin semester controller relevant lines =="
F=$(fd -a 'AdminSemesterController.java' . | head -1)
if [ -n "${F:-}" ]; then
awk 'NR>=20 && NR<=90 { printf "%4d %s\n", NR, $0 }' "$F"
fi
echo
echo "== exact tests for admin semester create path =="
rg -n "POST .*/admin/semesters|semesters|AdminTimetableSemesterCreateRequest|AdminSemester.*Test|semester.*year|createSemester|`@Valid`|ConstraintViolation|400|AdminSemester" src/test -g '*.java' | head -300 || true
echo
echo "== deterministic generated label lengths for edge cases =="
python3 - <<'PY'
samples = [1, 9, 10, 99, 100, 999, 1000, 9999, 10000, 1_000_000, 9_999_999, 10_000_000]
for year in samples:
sems = {
"FIRST": f"{year}1",
"SECOND": f"{year}2",
"SUMMER": f"{year}-여름",
"WINTER": f"{year}-겨울",
}
max_len = max(len(s) for s in sems.values())
print(f"year={year} lengths={s for s in sems.values() if len(s)==max_len} max_len={max_len}")
PY
echo
echo "== repository save method =="
F=$(fd -a 'AdminTimetableSemesterRepository.java' . | head -1)
if [ -n "${F:-}" ]; then
cat -n "$F"
fiRepository: BCSDLab/KOIN_API_V2
Length of output: 12855
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== generated label lengths for summer/winter edge cases =="
python3 - <<'PY'
samples = [10_000_000, 9_999_999, 1_000_000]
for year in samples:
for term in ["SUMMER", "WINTER", "FIRST", "SECOND"]:
if term in {"SUMMER", "WINTER"}:
label = f"{year}-여름" if term == "SUMMER" else f"{year}-겨울"
else:
label = f"{year}{term[0]}"
ok = len(label) <= 10
print(f"{term:8} year={year:>9} label={label:<15} length={len(label)} ok={ok}")
PY
echo
echo "== focused tests around /admin/semesters create =="
rg -n '"/admin/semesters"|AdminSemesterCreateRequest|AdminSemesterController|/admin/semesters|admin/semesters|POST.*semesters|semesters"' src/test/java src/main/java -g '*.java' | head -200 || trueRepository: BCSDLab/KOIN_API_V2
Length of output: 4197
Limit year so generated semester labels fit the 10-character field
@Positive accepts 10_000_000, and Semester.of then stores 10000000-여름 / 10000000-겨울, whose length is 11. Store the same created label back into Semester, so validation on the DTO is the user-visible failure point.
Proposed fix
+import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
`@NotNull`(message = "연도는 필수입니다.")
`@Positive`(message = "연도는 양수여야 합니다.")
+ `@Max`(value = 9_999_999, message = "연도는 9999999 이하여야 합니다.")
Integer year,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import jakarta.validation.constraints.NotNull; | |
| import jakarta.validation.constraints.Positive; | |
| @JsonNaming(SnakeCaseStrategy.class) | |
| public record AdminTimetableSemesterCreateRequest( | |
| @Schema(description = "연도", example = "2026", requiredMode = REQUIRED) | |
| @NotNull(message = "연도는 필수입니다.") | |
| @Positive(message = "연도는 양수여야 합니다.") | |
| Integer year, | |
| import jakarta.validation.constraints.Max; | |
| import jakarta.validation.constraints.NotNull; | |
| import jakarta.validation.constraints.Positive; | |
| `@JsonNaming`(SnakeCaseStrategy.class) | |
| public record AdminTimetableSemesterCreateRequest( | |
| `@Schema`(description = "연도", example = "2026", requiredMode = REQUIRED) | |
| `@NotNull`(message = "연도는 필수입니다.") | |
| `@Positive`(message = "연도는 양수여야 합니다.") | |
| `@Max`(value = 9_999_999, message = "연도는 9999999 이하여야 합니다.") | |
| Integer year, |
🤖 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/in/koreatech/koin/admin/semester/dto/AdminTimetableSemesterCreateRequest.java`
around lines 10 - 18, Update the year validation in
AdminTimetableSemesterCreateRequest to reject values whose generated semester
labels exceed the 10-character field, including 10,000,000; use the existing
validation mechanism and ensure the created label is stored back into Semester
so DTO validation remains the user-visible failure point.
| @Transactional | ||
| public void createSemester(AdminTimetableSemesterCreateRequest request) { | ||
| Semester semester = Semester.of(request.year(), request.term()); | ||
| validateDuplicateSemester(semester); | ||
| adminTimetableSemesterRepository.save(semester); | ||
| } | ||
|
|
||
| private void validateDuplicateSemester(Semester semester) { | ||
| if (adminTimetableSemesterRepository.existsBySemesterOrYearAndTerm( | ||
| semester.getSemester(), semester.getYear(), semester.getTerm() | ||
| )) { | ||
| throw CustomException.of(DUPLICATE_SEMESTER); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect existing exception translation for database uniqueness failures.
rg -n -C 3 -g '*.java' \
'DataIntegrityViolationException|ConstraintViolationException|DUPLICATE_SEMESTER|`@ExceptionHandler`' \
src/main/java
# Inspect schema definitions that create the semester uniqueness constraint.
rg -n -C 3 -g '*.sql' -g '*.xml' -g '*.yml' -g '*.yaml' \
'semester|unique' .Repository: BCSDLab/KOIN_API_V2
Length of output: 34901
Map the duplicate semester constraint failure to DUPLICATE_SEMESTER.
existsBySemesterOrYearAndTerm(...) and save(...) are not atomic. Concurrent requests for the same semester can both pass validation; the later transaction then fails at commit on UNIQUE KEY semester_UNIQUE instead of returning the declared DUPLICATE_SEMESTER response. Add an integration test that exercises the concurrent create race.
🤖 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/in/koreatech/koin/admin/semester/service/AdminTimetableSemesterService.java`
around lines 20 - 31, Update createSemester and its transaction handling to
catch the database unique-constraint failure for semester_UNIQUE during
save/commit and translate it to DUPLICATE_SEMESTER, while preserving the
existing pre-check. Add an integration test that runs concurrent identical
create requests and verifies the losing request returns the declared
duplicate-semester response.
🔍 개요
🚀 주요 변경 내용
💬 참고 사항
✅ Checklist (완료 조건)
Summary by CodeRabbit