feat: 어드민 강의 생성 API 구현 - #2322
Conversation
📝 WalkthroughWalkthroughAdds an administrator-only ChangesAdministrator lecture creation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant AdminLectureController
participant AdminLectureService
participant AdminSemesterRepository
participant AdminLectureRepository
Admin->>AdminLectureController: POST /admin/lectures
AdminLectureController->>AdminLectureService: createLectures(request)
AdminLectureService->>AdminSemesterRepository: resolve year and term
AdminLectureService->>AdminLectureRepository: check lecture duplicates
AdminLectureService->>AdminLectureRepository: saveAll(lectures)
AdminLectureService-->>AdminLectureController: return empty 200 response
🚥 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/lecture/dto/AdminLectureCreateRequest.java`:
- Around line 68-100: Update the required text fields regularNumber, target,
isEnglish, and isElearning in AdminLectureCreateRequest to reject empty and
whitespace-only input by replacing `@NotNull` with `@NotBlank`. For isEnglish and
isElearning, also apply the existing or appropriate allowed-value constraint if
their valid values are fixed, while preserving the current size limits and
validation messages.
In
`@src/main/java/in/koreatech/koin/admin/lecture/service/AdminLectureService.java`:
- Around line 35-46: Add a database-level unique constraint on the Lecture
business key (semester_date, code, and class), then update the save flow in
AdminLectureService to catch the resulting mapped constraint violation from
saveAll and translate it to DUPLICATE_LECTURE, preserving the existing duplicate
validation message and behavior.
🪄 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: 1e2ed87b-080c-495f-b5b9-b4f5067eb8d9
📒 Files selected for processing (11)
src/main/java/in/koreatech/koin/admin/history/enums/DomainType.javasrc/main/java/in/koreatech/koin/admin/lecture/controller/AdminLectureApi.javasrc/main/java/in/koreatech/koin/admin/lecture/controller/AdminLectureController.javasrc/main/java/in/koreatech/koin/admin/lecture/dto/AdminLectureCreateRequest.javasrc/main/java/in/koreatech/koin/admin/lecture/model/LectureKey.javasrc/main/java/in/koreatech/koin/admin/lecture/repository/AdminLectureRepository.javasrc/main/java/in/koreatech/koin/admin/lecture/repository/AdminSemesterRepository.javasrc/main/java/in/koreatech/koin/admin/lecture/service/AdminLectureService.javasrc/main/java/in/koreatech/koin/domain/timetableV3/exception/InvalidTermFormatException.javasrc/main/java/in/koreatech/koin/domain/timetableV3/model/Term.javasrc/main/java/in/koreatech/koin/global/code/ApiResponseCode.java
💤 Files with no reviewable changes (1)
- src/main/java/in/koreatech/koin/domain/timetableV3/exception/InvalidTermFormatException.java
| @Schema(description = "수강 인원", example = "25", requiredMode = REQUIRED) | ||
| @NotNull(message = "수강 인원은 필수입니다.") | ||
| @Size(max = 4, message = "수강 인원은 4자 이하여야 합니다.") | ||
| String regularNumber, | ||
|
|
||
| @Schema(description = "학부", example = "디자인ㆍ건축공학부", requiredMode = REQUIRED) | ||
| @NotBlank(message = "학부는 필수입니다.") | ||
| @Size(max = 30, message = "학부는 30자 이하여야 합니다.") | ||
| String department, | ||
|
|
||
| @Schema(description = "대상", example = "디자 1 건축", requiredMode = REQUIRED) | ||
| @NotNull(message = "대상은 필수입니다.") | ||
| @Size(max = 200, message = "대상은 200자 이하여야 합니다.") | ||
| String target, | ||
|
|
||
| @Schema(description = "교수", example = "황현식", requiredMode = NOT_REQUIRED) | ||
| @Size(max = 30, message = "교수명은 30자 이하여야 합니다.") | ||
| String professor, | ||
|
|
||
| @Schema(description = "영어 강의 여부", example = "N", requiredMode = REQUIRED) | ||
| @NotNull(message = "영어 강의 여부는 필수입니다.") | ||
| @Size(max = 2, message = "영어 강의 여부는 2자 이하여야 합니다.") | ||
| String isEnglish, | ||
|
|
||
| @Schema(description = "설계 학점", example = "0", requiredMode = REQUIRED) | ||
| @NotBlank(message = "설계 학점은 필수입니다.") | ||
| @Size(max = 2, message = "설계 학점은 2자 이하여야 합니다.") | ||
| String designScore, | ||
|
|
||
| @Schema(description = "이러닝 여부", example = "N", requiredMode = REQUIRED) | ||
| @NotNull(message = "이러닝 여부는 필수입니다.") | ||
| @Size(max = 2, message = "이러닝 여부는 2자 이하여야 합니다.") | ||
| String isElearning, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject blank values for required lecture fields.
@NotNull accepts "" and whitespace-only values. @Size(max = ...) does not reject them. The API can save blank values for regularNumber, target, isEnglish, and isElearning although these fields are required.
Use @NotBlank for required text fields. Use an allowed-value constraint for isEnglish and isElearning if the accepted values are fixed.
Proposed fix
- `@NotNull`(message = "수강 인원은 필수입니다.")
+ `@NotBlank`(message = "수강 인원은 필수입니다.")
`@Size`(max = 4, message = "수강 인원은 4자 이하여야 합니다.")
String regularNumber,
...
- `@NotNull`(message = "대상은 필수입니다.")
+ `@NotBlank`(message = "대상은 필수입니다.")
`@Size`(max = 200, message = "대상은 200자 이하여야 합니다.")
String target,
...
- `@NotNull`(message = "영어 강의 여부는 필수입니다.")
+ `@NotBlank`(message = "영어 강의 여부는 필수입니다.")
`@Size`(max = 2, message = "영어 강의 여부는 2자 이하여야 합니다.")
String isEnglish,
...
- `@NotNull`(message = "이러닝 여부는 필수입니다.")
+ `@NotBlank`(message = "이러닝 여부는 필수입니다.")
`@Size`(max = 2, message = "이러닝 여부는 2자 이하여야 합니다.")
String isElearning,📝 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.
| @Schema(description = "수강 인원", example = "25", requiredMode = REQUIRED) | |
| @NotNull(message = "수강 인원은 필수입니다.") | |
| @Size(max = 4, message = "수강 인원은 4자 이하여야 합니다.") | |
| String regularNumber, | |
| @Schema(description = "학부", example = "디자인ㆍ건축공학부", requiredMode = REQUIRED) | |
| @NotBlank(message = "학부는 필수입니다.") | |
| @Size(max = 30, message = "학부는 30자 이하여야 합니다.") | |
| String department, | |
| @Schema(description = "대상", example = "디자 1 건축", requiredMode = REQUIRED) | |
| @NotNull(message = "대상은 필수입니다.") | |
| @Size(max = 200, message = "대상은 200자 이하여야 합니다.") | |
| String target, | |
| @Schema(description = "교수", example = "황현식", requiredMode = NOT_REQUIRED) | |
| @Size(max = 30, message = "교수명은 30자 이하여야 합니다.") | |
| String professor, | |
| @Schema(description = "영어 강의 여부", example = "N", requiredMode = REQUIRED) | |
| @NotNull(message = "영어 강의 여부는 필수입니다.") | |
| @Size(max = 2, message = "영어 강의 여부는 2자 이하여야 합니다.") | |
| String isEnglish, | |
| @Schema(description = "설계 학점", example = "0", requiredMode = REQUIRED) | |
| @NotBlank(message = "설계 학점은 필수입니다.") | |
| @Size(max = 2, message = "설계 학점은 2자 이하여야 합니다.") | |
| String designScore, | |
| @Schema(description = "이러닝 여부", example = "N", requiredMode = REQUIRED) | |
| @NotNull(message = "이러닝 여부는 필수입니다.") | |
| @Size(max = 2, message = "이러닝 여부는 2자 이하여야 합니다.") | |
| String isElearning, | |
| `@Schema`(description = "수강 인원", example = "25", requiredMode = REQUIRED) | |
| `@NotBlank`(message = "수강 인원은 필수입니다.") | |
| `@Size`(max = 4, message = "수강 인원은 4자 이하여야 합니다.") | |
| String regularNumber, | |
| `@Schema`(description = "학부", example = "디자인ㆍ건축공학부", requiredMode = REQUIRED) | |
| `@NotBlank`(message = "학부는 필수입니다.") | |
| `@Size`(max = 30, message = "학부는 30자 이하여야 합니다.") | |
| String department, | |
| `@Schema`(description = "대상", example = "디자 1 건축", requiredMode = REQUIRED) | |
| `@NotBlank`(message = "대상은 필수입니다.") | |
| `@Size`(max = 200, message = "대상은 200자 이하여야 합니다.") | |
| String target, | |
| `@Schema`(description = "교수", example = "황현식", requiredMode = NOT_REQUIRED) | |
| `@Size`(max = 30, message = "교수명은 30자 이하여야 합니다.") | |
| String professor, | |
| `@Schema`(description = "영어 강의 여부", example = "N", requiredMode = REQUIRED) | |
| `@NotBlank`(message = "영어 강의 여부는 필수입니다.") | |
| `@Size`(max = 2, message = "영어 강의 여부는 2자 이하여야 합니다.") | |
| String isEnglish, | |
| `@Schema`(description = "설계 학점", example = "0", requiredMode = REQUIRED) | |
| `@NotBlank`(message = "설계 학점은 필수입니다.") | |
| `@Size`(max = 2, message = "설계 학점은 2자 이하여야 합니다.") | |
| String designScore, | |
| `@Schema`(description = "이러닝 여부", example = "N", requiredMode = REQUIRED) | |
| `@NotBlank`(message = "이러닝 여부는 필수입니다.") | |
| `@Size`(max = 2, message = "이러닝 여부는 2자 이하여야 합니다.") | |
| String isElearning, |
🤖 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/lecture/dto/AdminLectureCreateRequest.java`
around lines 68 - 100, Update the required text fields regularNumber, target,
isEnglish, and isElearning in AdminLectureCreateRequest to reject empty and
whitespace-only input by replacing `@NotNull` with `@NotBlank`. For isEnglish and
isElearning, also apply the existing or appropriate allowed-value constraint if
their valid values are fixed, while preserving the current size limits and
validation messages.
| if (!lectureKeys.add(lectureKey) || adminLectureRepository.existsBySemesterAndCodeAndLectureClass( | ||
| semester.getSemester(), lecture.code(), lecture.lectureClass() | ||
| )) { | ||
| throw CustomException.of( | ||
| DUPLICATE_LECTURE, | ||
| "semester: " + semester.getSemester() + ", code: " + lecture.code() | ||
| + ", lectureClass: " + lecture.lectureClass() | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| adminLectureRepository.saveAll(request.toEntities(semester.getSemester())); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -t f 'Lecture.java' src
rg -n -C 4 '`@Table`|`@UniqueConstraint`|uniqueConstraints|semester|lectureClass|lecture_class' src
rg -n -i -C 4 'create table.*lecture|alter table.*lecture|unique.*lecture|lecture_class' .Repository: BCSDLab/KOIN_API_V2
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- AdminLectureService ---\n'
fd -t f 'AdminLectureService.java' src -x sh -c 'wc -l "$1"; sed -n "1,120p" "$1"' sh {}
printf '\n--- AdminLectureRepository definitions/usages ---\n'
fd -t f 'AdminLectureRepository.java|AdminLecture.*Repository.java' src -x sh -c 'echo "== $1 =="; cat -n "$1"' sh {}
printf '\n--- Lecture model entity ---\n'
fd -t f 'Lecture.java' src -x sh -c 'echo "== $1 =="; cat -n "$1"' sh {}
printf '\n--- targeted DDL/migrations references ---\n'
git ls-files | rg -i '(migration|flyway|liquibase|ddl|schema|lecture)' | head -200 | while read -r f; do
case "$f" in
*.java|*.sql|*.yml|*.yaml|*.properties|*.xml)
if rg -qi 'lectures|uniqueConstraints|semester|code|lecture_class|lectureclass|lecture_class' "$f"; then
echo "== $f =="
rg -n -C 3 -i 'lectures|uniqueConstraints| semester_date| code| lecture_class| lectureClass|lectureclass' "$f" | head -80
fi
;;
esac
doneRepository: BCSDLab/KOIN_API_V2
Length of output: 248
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- AdminLectureService ---'
fd -t f 'AdminLectureService.java' src -x sh -c 'wc -l "$1"; sed -n "1,140p" "$1"' sh {}
echo
echo '--- AdminLecture repository definitions/usages ---'
fd -t f 'AdminLecture.*Repository.java' src -x sh -c 'echo "==$1=="; cat -n "$1"' sh {} || true
echo
echo '--- lecture repository ---'
fd -t f 'LectureRepository.java' src -x sh -c 'echo "==$1=="; cat -n "$1"' sh {}
echo
echo '--- Lecture model entity ---'
fd -t f 'Lecture.java' src -x sh -c 'echo "==$1=="; cat -n "$1"' sh {}
echo
echo '--- targeted DDL/migrations references ---'
git ls-files | rg -i '(migration|flyway|liquibase|ddl|schema|lecture)' | head -200 | while read -r f; do
case "$f" in
*.java|*.sql|*.yml|*.yaml|*.properties|*.xml)
if rg -qi 'lectures|uniqueConstraints|semester_date| code| lecture_class|lectureClass|lectureclass' "$f"; then
echo "==$f=="
rg -n -C 3 -i 'lectures|uniqueConstraints|semester_date| code| lecture_class|lectureClass|lectureclass' "$f" | head -120
fi
;;
esac
doneRepository: BCSDLab/KOIN_API_V2
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- files likely containing lectures DDL/migrations/config ---'
git ls-files | rg -i 'lectures|lecture|unique|ddl|schema|migration|flyway|liquibase|sql|yml|yaml|properties|pom|gradle' | head -300
echo
echo '--- exact unique/DUPLECTURE references ---'
rg -n -C 3 'DUPLICATE_LECTURE|uniqueConstraints|uniqueConstraints|constraint.*lectures|unique.*lectures|lectures\(|UNIQUE.*semester_date|UNIQUE|lecture_class|LECTURES' src main .github --glob '!build/**' --glob '!dist/**' || true
echo
echo '--- exception mapper around constraint failures ---'
fd -t f 'GlobalExceptionHandler.java|ExceptionHandler.java|*Exception*Handler.java|ApiResponseCode.java|ExceptionMapper.java|DatabaseAdapterResolver.java' src -x sh -c 'echo "==$1=="; sed -n "1,220p" "$1"' sh {} | rg -n -C 4 'DUPLICATE_LECTURE|DataIntegrity|ConstraintViolation|Duplicate|ex\.|constraint|unique|lectures' || trueRepository: BCSDLab/KOIN_API_V2
Length of output: 50375
Add a database guarantee for lecture identity.
Lecture maps lectures(semester_date, code, class) but has no unique constraint, and the current code only does an in-transaction existsBy... before saveAll(). Concurrent requests can both see no row and insert duplicate (semester, code, lectureClass) entries. Add a unique constraint for this business key and handle mapped constraint failures with DUPLICATE_LECTURE.
🤖 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/lecture/service/AdminLectureService.java`
around lines 35 - 46, Add a database-level unique constraint on the Lecture
business key (semester_date, code, and class), then update the save flow in
AdminLectureService to catch the resulting mapped constraint violation from
saveAll and translate it to DUPLICATE_LECTURE, preserving the existing duplicate
validation message and behavior.
🔍 개요
🚀 주요 변경 내용
💬 참고 사항
✅ Checklist (완료 조건)
Summary by CodeRabbit
New Features
Bug Fixes