Skip to content

v0.5.1#70

Merged
rktclgh merged 13 commits into
mainfrom
develop
Mar 18, 2026
Merged

v0.5.1#70
rktclgh merged 13 commits into
mainfrom
develop

Conversation

@rktclgh

@rktclgh rktclgh commented Mar 18, 2026

Copy link
Copy Markdown
Owner

📢 기능 설명

필요시 실행결과 스크린샷 첨부

연결된 issue

연결된 issue를 자동으로 닫기 위해 아래 {이슈넘버}를 입력해주세요.

close #{이슈넘버}



🩷 Approve 하기 전 확인해주세요!

  • 리뷰어가 확인해줬으면 하는 사항 적어주세요.
  • [ ]

✅ 체크리스트

  • PR 제목 규칙 잘 지켰는가?
  • 추가/수정사항을 설명하였는가?
  • 이슈넘버를 적었는가?
  • Approve 하기 전 확인 사항 체크했는가?

rktclgh and others added 13 commits March 16, 2026 14:00
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 관련 값은 주석 처리
내부 캐시 구조는 이미 들어가 있음
@coderabbitai

coderabbitai Bot commented Mar 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 240c0563-f65d-4c17-ab96-b272e6872911

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rktclgh rktclgh merged commit 049d1e3 into main Mar 18, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Vlainter_BackEnd Mar 18, 2026
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, 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

  • Student Mode Feature: Introduced a new 'Student Mode' service, allowing users to manage academic courses, upload lecture materials and past exams, and leverage AI-powered study tools.
  • AI-Powered Academic Tools: Expanded AI orchestration capabilities to generate university exam questions, summarize course materials, refine past exam questions, recover questions from OCR, and evaluate student answers.
  • Enhanced Document Ingestion: Improved the document ingestion pipeline to support course materials, including extraction of visual assets (PDF pages, PPT slides, embedded images) and advanced OCR processing for image-based documents.
  • Academic Data Integration: Implemented a new academic search service that integrates with a public API to search and cache university and department information, which can be linked to user profiles and courses.
  • Security Enhancements: Refined the suspicious request blocking filter to allow legitimate public asset requests and preview bots to bypass blocking, while also implementing rate-limited logging for security events.

🧠 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
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/deploy-main.yml
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines 294 to +297
docChunkEmbeddingRepository.deleteAllByUserIdAndUserFileId(targetOwnerId, target.id)
documentIngestionJobRepository.deleteAllByUserIdAndDocumentFileId(targetOwnerId, target.id)
studentCourseMaterialVisualAssetRepository.deleteAllByUserFile_Id(target.id)
studentCourseMaterialRepository.findByUserFile_Id(target.id)?.let { studentCourseMaterialRepository.delete(it) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +2057 to +2060
val readyPastExamMaterials = studentCourseMaterialRepository.findAllByCourse_IdOrderByCreatedAtDesc(sourceSession.courseId)
.filter { material ->
resolveMaterialKind(material) == StudentCourseMaterialKind.PAST_EXAM &&
material.latestIngestionJob()?.status == DocumentIngestionStatus.READY

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +53 to +56
val localResults = academicUniversityRepository.searchByKeyword(normalizeKeyword(normalizedKeyword))
.map { it.toResponse() }
.take(MAX_RESULT_SIZE)
if (localResults.isNotEmpty() || academyInfoServiceKey.isBlank()) return localResults

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

현재 maxretry = 1로 설정되어 있어 단 한 번의 444 응답으로도 IP가 차단됩니다. 이는 프로브 공격을 빠르게 차단하는 데 효과적일 수 있지만, 의도치 않은 정상적인 요청이 444를 반환하는 경우에도 즉시 차단될 수 있습니다. 오탐(false positive) 가능성을 줄이기 위해 maxretry 값을 2 또는 3으로 늘리는 것을 고려해 볼 수 있습니다. 이렇게 해도 여전히 공격적인 프로브는 효과적으로 차단될 것입니다.

Comment on lines +154 to +176
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

cleanupTarget 함수 내의 when 문은 특정 학교 및 학과 이름에 대한 하드코딩된 정리 로직을 포함하고 있습니다. 현재는 두 가지 케이스만 있지만, 향후 더 많은 학교/학과에 대한 정리 규칙이 필요해질 경우 이 블록이 비대해지고 유지보수가 어려워질 수 있습니다. 이러한 정리 규칙을 외부 설정 파일이나 데이터베이스에서 관리하는 등 좀 더 유연하고 데이터 기반의 접근 방식을 고려해 볼 수 있습니다.

Comment on lines +466 to +518
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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

evaluateCourseExamAnswersBatch 함수의 프롬프트는 매우 상세하고 많은 규칙을 포함하고 있습니다. 이는 LLM의 성능을 높이는 데 도움이 되지만, 프롬프트가 너무 길어지면 토큰 비용이 증가하고, 프롬프트 자체의 유지보수가 어려워질 수 있습니다. 반복되는 규칙이나 구조화된 정보는 별도의 함수로 분리하거나, 데이터 구조를 활용하여 프롬프트 생성을 좀 더 모듈화하는 방안을 고려해 볼 수 있습니다.

Comment on lines +1886 to +1895
val uploadedKeys = mutableListOf<String>()
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(object : TransactionSynchronization {
override fun afterCompletion(status: Int) {
if (status != TransactionSynchronization.STATUS_COMMITTED) {
uploadedKeys.forEach(::deleteObjectQuietly)
}
}
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

replaceCourseMaterialVisualAssets 함수에서 TransactionSynchronizationManager.registerSynchronization을 사용하여 트랜잭션 커밋 실패 시 S3에 업로드된 파일을 롤백하는 로직은 좋습니다. 다만, uploadedKeys.forEach(::deleteObjectQuietly)catch 블록과 afterCompletion 콜백 모두에서 호출될 수 있습니다. deleteObjectQuietly는 멱등성(idempotent)을 가지므로 큰 문제는 없지만, 불필요한 중복 호출을 피하기 위해 로직을 좀 더 명확히 분리하거나, uploadedKeys를 비우는 시점을 조정하는 것을 고려해 볼 수 있습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant