Conversation
JOB_SEEKER, STUDENT /api/users/me 응답 확장 서비스 모드 변경 API 대학생 프로필 추가 대학교/학과 저장 API 마이페이지에서 수정 가능 /content 진입 시 서비스 모드 기준 분기 최초 진입용 서비스 모드 선택 페이지 추가 대학/학과 검색 구조 academic_universities, academic_departments 내부 화이트리스트 캐시 테이블 추가 대학/학과는 검색 결과에서 선택한 ID만 저장 가능 수동 텍스트만으로는 저장 불가 대학생 랜딩/셸 ContentTopNav, Sidebar, MobileSidebarDrawer 재사용 학생용 사이드바 메뉴 연결 과목 도메인 1차 student_courses 테이블 과목 생성 API 내 과목 목록 조회 API 대학생 랜딩에서 과목 등록/목록 UI 연결 과목별 자료 업로드 1차 COURSE_MATERIAL 파일 타입 추가 student_course_materials 테이블 과목별 자료 목록 조회 API 과목별 PDF/DOCX/PPTX 업로드 API 대학생 랜딩에서 과목 카드별 자료 업로드/최근 자료 목록 UI 연결 외부 대학 API 대응 현재 academyinfo 키가 아직 활성화되지 않아 .env, .env-deploy 관련 값은 주석 처리 내부 캐시 구조는 이미 들어가 있음
feat/v0.5.1
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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 |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a comprehensive 'Student Mode' feature, enabling users to manage academic courses, upload course materials, generate AI-powered exam questions and material summaries, and track wrong answers. It integrates with a public academic data API for university and department search, enhances document ingestion with visual asset extraction and improved OCR, and refines security measures for suspicious requests. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18b44f4b81
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| docChunkEmbeddingRepository.deleteAllByUserIdAndUserFileId(targetOwnerId, target.id) | ||
| documentIngestionJobRepository.deleteAllByUserIdAndDocumentFileId(targetOwnerId, target.id) | ||
| studentCourseMaterialVisualAssetRepository.deleteAllByUserFile_Id(target.id) | ||
| studentCourseMaterialRepository.findByUserFile_Id(target.id)?.let { studentCourseMaterialRepository.delete(it) } |
There was a problem hiding this comment.
Delete derived visual assets when removing course materials
For COURSE_MATERIAL, this branch only deletes the StudentCourseMaterialVisualAsset rows and the original uploaded file. The preview images uploaded by uploadCourseVisualAsset() each have their own S3 storageKey, and those keys are never enumerated or deleted here, so removing an analyzed course material leaves all derived PNG/JPG assets orphaned in the bucket. Re-uploading and deleting lecture materials will leak unreachable objects indefinitely.
Useful? React with 👍 / 👎.
| val readyPastExamMaterials = studentCourseMaterialRepository.findAllByCourse_IdOrderByCreatedAtDesc(sourceSession.courseId) | ||
| .filter { material -> | ||
| resolveMaterialKind(material) == StudentCourseMaterialKind.PAST_EXAM && | ||
| material.latestIngestionJob()?.status == DocumentIngestionStatus.READY |
There was a problem hiding this comment.
Persist selected past-exam sources for session detail views
buildQuestionSourceContexts() reconstructs source files by scanning all ready past exams in the course, but createCourseSession() only stores sourceMaterialCount, not which past-exam materials were actually selected. When a user generated a practice exam from a subset of uploads—or two uploads contain the same question text—the later detail/retest view can attach sourceFileName and sourceVisualAssets from an unrelated file, so the UI shows the wrong origin for the question.
Useful? React with 👍 / 👎.
| val localResults = academicUniversityRepository.searchByKeyword(normalizeKeyword(normalizedKeyword)) | ||
| .map { it.toResponse() } | ||
| .take(MAX_RESULT_SIZE) | ||
| if (localResults.isNotEmpty() || academyInfoServiceKey.isBlank()) return localResults |
There was a problem hiding this comment.
Keep querying the public API after partial cache hits
The local academic tables are only populated on demand (resolveOrCreate…) or by the new targeted import runner, so a cache hit does not mean the result set is complete. Returning immediately on any local match means that once one cached university matches a keyword, /api/academics/universities/search stops consulting the public API and only returns that partial subset; for example, after importing one 아주… school, other official 아주… matches disappear. searchDepartments() has the same early-return pattern at line 93.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Code Review
This pull request introduces several new features and improvements, including the implementation of fail2ban to block probe attacks, academic search functionality, and AI-powered exam question generation and course material summarization. The maxretry value in vlainter-probe.local should be increased to reduce false positives. The hardcoded cleanup logic in AcademicMetadataImportRunner.kt should be refactored to use external configuration or a database. The lengthy prompt in InterviewAiOrchestrator.kt's evaluateCourseExamAnswersBatch function should be modularized to improve maintainability and reduce token costs. The redundant calls to deleteObjectQuietly in replaceCourseMaterialVisualAssets should be removed.
| backend = auto | ||
| port = http,https | ||
| findtime = 600 | ||
| maxretry = 1 |
| val deletedDepartments = when { | ||
| target.normalizedSchoolName == normalize("대림대학교") && target.normalizedMajorName == normalize("자동차") -> | ||
| academicSearchService.deleteDepartmentsByExactNames( | ||
| universityName = "대림대학교", | ||
| departmentNames = listOf( | ||
| "미래자동차공학부", | ||
| "미래자동차공학과", | ||
| "미래자동차학부", | ||
| "미래자동차과", | ||
| "자동차학부", | ||
| "자동차과", | ||
| "자동차공학과(1년)", | ||
| "자동차공학과" | ||
| ) | ||
| ) | ||
|
|
||
| target.normalizedSchoolName == normalize("아주대학교") && target.normalizedMajorName == normalize("경영학과") -> | ||
| academicSearchService.deleteDepartmentsByExactNames( | ||
| universityName = "아주대학교", | ||
| departmentNames = listOf("경영학과") | ||
| ) | ||
|
|
||
| else -> 0 |
| val prompt = """ | ||
| ${evaluationSystemRole(responseLanguage, "university written exam grader")} | ||
| ${jsonLanguageInstruction(responseLanguage)} | ||
|
|
||
| [대학교] | ||
| $universityName | ||
|
|
||
| [학과] | ||
| $departmentName | ||
|
|
||
| [과목명] | ||
| $courseName | ||
|
|
||
| [출제 모드] | ||
| $generationMode | ||
|
|
||
| [난이도] | ||
| ${difficultyLevel ?: "족보 기준 반영"} | ||
|
|
||
| 아래 각 문제를 서로 독립적으로 채점하고 JSON만 반환하세요. | ||
|
|
||
| 출력 JSON 스키마: | ||
| { | ||
| "items": [ | ||
| { | ||
| "key": "stable key", | ||
| "score": 0~maxScore 정수, | ||
| "passScore": 통과 기준 점수 정수, | ||
| "feedback": "부분점수 근거와 보강 포인트를 포함한 총평(2~5문장)" | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| 채점 규칙: | ||
| - 반드시 모든 입력 key를 유지해서 반환 | ||
| - score는 0 이상 maxScore 이하의 정수 | ||
| - 부분점수를 적극 허용할 것 | ||
| - questionStyle=DEFINITION 또는 ESSAY: 핵심 개념의 정확성, 범위 충실도, 비교/적용 설명을 본다 | ||
| - questionStyle=CALCULATION: 식 설정, 변수 해석, 단위, 계산 과정, 최종 결론을 본다 | ||
| - questionStyle=CODING: 답안이 실제 코드 형태인지, 요구 기능을 충족하는지, 예시 입출력 또는 제시된 조건을 만족하는지, 핵심 자료구조/알고리즘 선택이 적절한지 본다 | ||
| - questionStyle=CODING: 사용 언어는 감점 요소가 아니며, 학교 교육과정 차이를 고려해 Java, C, C++, Python 등 어떤 언어로 작성해도 로직이 타당하면 인정할 것 | ||
| - questionStyle=CODING: 컴파일 오류 가능성이 높은 문법 오류, 실행 자체가 불가능한 수준의 선언/구문 오류, 문제 요구를 깨는 API 오용은 명확한 감점 요소로 반영할 것 | ||
| - questionStyle=CODING: 단순 문법 실수 하나만으로 0점을 주지 말고, 로직이 맞으면 부분점수를 줄 것 | ||
| - questionStyle=PRACTICAL: 명령어, 절차, 시스템 조작 흐름의 정확성, 예제 만족 여부, 중요한 예외 처리를 본다 | ||
| - referenceExample이 있으면 예시 충족 여부를 함께 보되, 표현 차이만으로 감점하지 말 것 | ||
| - canonicalAnswer와 gradingCriteria는 채점 기준이며, 강의자료 밖 정답을 요구하지 말 것 | ||
| - feedback에는 무엇을 맞췄고 무엇이 부족했는지, 왜 그 점수가 나왔는지 분명히 적을 것 | ||
| - feedback은 ${responseLanguage.displayLanguageName()}로 작성할 것 | ||
| - 반드시 JSON만 출력 | ||
|
|
||
| [items] | ||
| ${objectMapper.writeValueAsString(items)} | ||
| """.trimIndent() |
| val uploadedKeys = mutableListOf<String>() | ||
| if (TransactionSynchronizationManager.isActualTransactionActive()) { | ||
| TransactionSynchronizationManager.registerSynchronization(object : TransactionSynchronization { | ||
| override fun afterCompletion(status: Int) { | ||
| if (status != TransactionSynchronization.STATUS_COMMITTED) { | ||
| uploadedKeys.forEach(::deleteObjectQuietly) | ||
| } | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
replaceCourseMaterialVisualAssets 함수에서 TransactionSynchronizationManager.registerSynchronization을 사용하여 트랜잭션 커밋 실패 시 S3에 업로드된 파일을 롤백하는 로직은 좋습니다. 다만, uploadedKeys.forEach(::deleteObjectQuietly)가 catch 블록과 afterCompletion 콜백 모두에서 호출될 수 있습니다. deleteObjectQuietly는 멱등성(idempotent)을 가지므로 큰 문제는 없지만, 불필요한 중복 호출을 피하기 위해 로직을 좀 더 명확히 분리하거나, uploadedKeys를 비우는 시점을 조정하는 것을 고려해 볼 수 있습니다.
📢 기능 설명
필요시 실행결과 스크린샷 첨부
연결된 issue
연결된 issue를 자동으로 닫기 위해 아래 {이슈넘버}를 입력해주세요.
close #{이슈넘버}
🩷 Approve 하기 전 확인해주세요!
✅ 체크리스트