Skip to content

[FEAT] 이력서 기반 면접 목록 이력서 기반 폴더링 API#320

Merged
unifolio0 merged 2 commits intodevelopfrom
feat/#317
Jan 20, 2026
Merged

[FEAT] 이력서 기반 면접 목록 이력서 기반 폴더링 API#320
unifolio0 merged 2 commits intodevelopfrom
feat/#317

Conversation

@unifolio0
Copy link
Contributor

closed #317

작업 내용

스크린샷

참고 사항

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 질문 생성 이력 목록을 페이지네이션과 함께 조회하는 기능 추가
    • 상태별(PENDING, COMPLETED, FAILED) 질문 생성 이력 필터링 기능 추가
    • 조회 응답에 이력서 및 포트폴리오 정보 포함
  • Documentation

    • 질문 생성 이력 목록 조회 API 문서 추가

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

이력서 기반 면접 질문 생성 기록 목록을 조회하는 기능이 추가되었습니다. 새로운 API 엔드포인트, 다수의 DTO, 리포지토리 쿼리 메서드, 테스트 픽스처 및 문서화가 도입되었으며, 기존 상태 응답 타입이 변경되었습니다.

Changes

코호트 / 파일(들) 변경 사항
컨트롤러 및 엔드포인트
api/src/main/java/com/samhap/kokomen/interview/controller/ResumeBasedInterviewController.java
GET /questions/generations 엔드포인트 추가로 사용자의 질문 생성 기록을 페이지 형식으로 반환; 상태 필터링 및 페이지 처리 지원; getGenerationStatus 반환 타입을 QuestionGenerationStatusResponse에서 QuestionGenerationStateResponse로 변경; ID 파싱 검증 강화
서비스 계층
api/src/main/java/com/samhap/kokomen/interview/service/ResumeBasedInterviewService.java
findMyQuestionGenerations 메서드 추가로 회원의 생성 기록을 페이지 형식으로 조회; 기본 상태 필터링(PENDING, COMPLETED) 도입; getQuestionGenerationStatus 반환 타입 변경
응답 DTO
api/src/main/java/com/samhap/kokomen/interview/service/dto/ResumeQuestionGenerationPageResponse.java, ResumeQuestionGenerationResponse.java, QuestionGenerationStateResponse.java, ResumeInfo.java, PortfolioInfo.java
페이지 응답 래퍼, 생성 기록 상세 정보, 상태 응답, 이력서 및 포트폴리오 정보 DTO 5개 추가
삭제된 DTO
api/src/main/java/com/samhap/kokomen/interview/service/dto/QuestionGenerationStatusResponse.java
status 필드 포함 기존 응답 DTO 삭제
리포지토리
common/src/main/java/com/samhap/kokomen/interview/repository/ResumeQuestionGenerationRepository.java
findByMemberIdAndStateInfindByMemberIdAndState 쿼리 메서드 추가; EntityGraph로 연관 데이터(memberResume, memberPortfolio) 함께 조회
테스트
api/src/test/java/com/samhap/kokomen/global/fixture/interview/ResumeQuestionGenerationFixtureBuilder.java, api/src/test/java/com/samhap/kokomen/interview/controller/ResumeBasedInterviewControllerTest.java
테스트 픽스처 빌더 클래스 신규 추가 및 목록 조회, 상태 필터링, 페이지네이션 테스트 198줄 추가
문서화
api/src/docs/asciidoc/index.adoc
이력서 기반 질문 생성 기록 목록 조회 API 문서화 섹션(HTTP 요청/응답, 헤더, 파라미터, 필드) 추가
정리
common/src/main/java/com/samhap/kokomen/interview/domain/InterviewMode.java
불필요한 공백 줄 제거

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as ResumeBasedInterviewController
    participant Service as ResumeBasedInterviewService
    participant Repository as ResumeQuestionGenerationRepository
    participant Database as DB

    Client->>Controller: GET /questions/generations?state=COMPLETED&page=0&size=20
    Controller->>Controller: 인증 정보 검증
    Controller->>Service: findMyQuestionGenerations(memberId, state, pageable)
    Service->>Service: 상태 필터링 결정 (null이면 기본값)
    Service->>Repository: findByMemberIdAndState(In)(memberId, states, pageable)
    Repository->>Database: SELECT 쿼리 (EntityGraph로 resume/portfolio 함께)
    Database-->>Repository: Page<ResumeQuestionGeneration>
    Repository-->>Service: 페이지 데이터 반환
    Service->>Service: ResumeQuestionGenerationResponse.from() 매핑
    Service->>Service: ResumeQuestionGenerationPageResponse.of() 래핑
    Service-->>Controller: ResumeQuestionGenerationPageResponse
    Controller-->>Client: JSON 응답 (data, currentPage, totalCount, totalPages, hasNext)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 분

Possibly related PRs

Suggested reviewers

  • nak-honest
  • kargowild

Poem

🐰 목록 조회의 새 길을 닦으며
질문들의 기록을 페이지에 담고
상태별 필터링으로 한층 정교해진
면접 준비의 여정이여
토끼의 발걸음처럼 또 한 발 나아가네 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive 연결된 이슈 #317은 구체적인 요구사항 없이 제목만 제공되어 있어 충분한 검증을 할 수 없습니다. 이슈 #317에 구체적인 요구사항, 수락 기준, 또는 체크리스트를 추가하여 구현 목표를 명확히 해주세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 변경사항의 주요 목표인 이력서 기반 질문 생성 목록 조회 및 필터링 API 기능을 명확하게 반영하고 있습니다.
Description check ✅ Passed PR 설명에서 관련 이슈 #317을 참조하고 있어 변경사항과 연결되어 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항은 이력서 기반 질문 생성 목록 조회 및 상태 필터링 기능 구현과 관련되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@unifolio0 unifolio0 self-assigned this Jan 20, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @unifolio0, 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!

이 풀 리퀘스트는 사용자가 자신의 이력서 기반 면접 질문 생성 기록을 조회할 수 있는 기능을 추가합니다. 이 기능은 페이지네이션과 상태 필터링을 지원하여 사용자가 특정 상태의 기록을 효율적으로 검색하고 관리할 수 있도록 합니다. 이를 통해 사용자 경험을 개선하고, 질문 생성 과정의 투명성을 높입니다.

Highlights

  • 새로운 API 엔드포인트 추가: 이력서 기반 질문 생성 기록 목록을 조회하는 새로운 GET API 엔드포인트 /api/v1/interviews/resume-based/questions/generations가 추가되었습니다.
  • DTO 리팩토링 및 추가: 기존 QuestionGenerationStatusResponse DTO가 QuestionGenerationStateResponse로 이름이 변경 및 리팩토링되었으며, 이력서 및 포트폴리오 정보를 위한 ResumeInfo, PortfolioInfo DTO와 페이지네이션 응답을 위한 ResumeQuestionGenerationPageResponse, 단일 기록 상세를 위한 ResumeQuestionGenerationResponse DTO가 새로 추가되었습니다.
  • 질문 생성 기록 조회 로직 구현: ResumeBasedInterviewService에 사용자의 질문 생성 기록을 멤버 ID, 상태 필터링, 페이지네이션을 통해 조회하는 findMyQuestionGenerations 메서드가 구현되었습니다.
  • 테스트 및 문서 업데이트: 새로운 API 엔드포인트에 대한 통합 테스트 케이스가 추가되었고, API 문서 (index.adoc)에 해당 엔드포인트의 사용법이 포함되었습니다.

🧠 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.

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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


데이터 흐름 따라, 기록 쌓여 지혜 되니, 새로운 길 열다.

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.

Copy link

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

Choose a reason for hiding this comment

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

Code Review

이 Pull Request는 이력서 기반 질문 생성 기록을 조회하는 API를 추가하는 것을 주요 내용으로 하고 있습니다. 새로운 API 엔드포인트, 서비스 로직, DTO, 그리고 관련 테스트 코드가 포함되어 있습니다. 전반적으로 코드 품질이 높고, 특히 N+1 문제 방지를 위해 @EntityGraph를 사용한 점이 인상적입니다. 몇 가지 개선점을 제안드렸으니 확인 부탁드립니다.

Comment on lines 151 to 152
log.error("Exception :: state: {}, message: {}, stackTrace: ", HttpStatus.INTERNAL_SERVER_ERROR,
e.getMessage(), e);

Choose a reason for hiding this comment

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

medium

로그 메시지의 state 필드에 HttpStatus.INTERNAL_SERVER_ERROR를 출력하고 있어 혼란을 줄 수 있습니다. 예외 객체 e를 로깅하면 메시지와 스택 트레이스가 모두 포함되므로, 로그 메시지를 더 간결하게 수정하는 것을 제안합니다.

            log.error("Exception in callbackInterviewProceedBedrockFlow", e);

Comment on lines 288 to 289
log.error("Exception :: state: {}, message: {}, stackTrace: ", HttpStatus.INTERNAL_SERVER_ERROR,
e.getMessage(), e);

Choose a reason for hiding this comment

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

medium

로그 메시지의 state 필드에 HttpStatus.INTERNAL_SERVER_ERROR를 출력하고 있어 혼란을 줄 수 있습니다. 예외 객체 e를 로깅하면 메시지와 스택 트레이스가 모두 포함되므로, 로그 메시지를 더 간결하게 수정하는 것을 제안합니다. 이 파일의 다른 예외 로깅과 일관성을 맞추는 것이 좋습니다.

            log.error("Exception in callbackAnswerFeedbackBedrockFlow", e);

Comment on lines +126 to +129
List<ResumeQuestionGenerationState> defaultStates = List.of(
ResumeQuestionGenerationState.PENDING,
ResumeQuestionGenerationState.COMPLETED
);

Choose a reason for hiding this comment

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

medium

defaultStates 리스트가 getResumeQuestionGenerations 메소드가 호출될 때마다 생성되고 있습니다. 이 리스트는 변경되지 않으므로 클래스 레벨에서 private static final List<ResumeQuestionGenerationState> DEFAULT_FILTER_STATES = List.of(ResumeQuestionGenerationState.PENDING, ResumeQuestionGenerationState.COMPLETED); 와 같이 상수로 선언하여 재사용하는 것을 고려해보세요. 이렇게 하면 불필요한 객체 생성을 피하고 코드의 의도를 더 명확하게 할 수 있습니다.

@github-actions
Copy link

github-actions bot commented Jan 20, 2026

Test Results

 40 files   40 suites   58s ⏱️
236 tests 235 ✅ 1 💤 0 ❌
238 runs  237 ✅ 1 💤 0 ❌

Results for commit 801c6ba.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@api/src/main/java/com/samhap/kokomen/interview/service/InterviewProceedBedrockFlowAsyncService.java`:
- Around line 151-152: The log line in InterviewProceedBedrockFlowAsyncService
mistakenly labels a status value as "state": update the log call so labels and
values match by either passing the actual state variable (e.g.,
interviewProceedStateKey or the real state value) in place of
HttpStatus.INTERNAL_SERVER_ERROR when you intend to log state, or change the
label from "state" to "status" if you intend to log the HTTP status; keep the
exception parameter (e) as the last argument and preserve the log.error(...)
signature so message, substituted value, and exception are correctly ordered.
- Around line 288-289: The log line in InterviewProceedBedrockFlowAsyncService
(inside the method that catches exceptions) uses the label "state" but passes an
HttpStatus; update the log template and parameters so labels match values:
replace "state" with "status" or include an identifier like curAnswerId
alongside status; ensure the call to log.error includes the descriptive message,
the correct status (HttpStatus.INTERNAL_SERVER_ERROR) and the exception e
(e.getMessage(), e) in the same order as the template. Locate the log.error in
InterviewProceedBedrockFlowAsyncService.java and adjust the message string and
arguments to keep labels consistent with the passed values.

In
`@api/src/main/java/com/samhap/kokomen/interview/service/ResumeBasedInterviewService.java`:
- Around line 83-95: The helper methods findMemberResume and findMemberPortfolio
currently return null when an ID is provided but the entity isn't found,
silently allowing success; change them to throw a 400-level exception instead
(e.g., ResponseStatusException(HttpStatus.BAD_REQUEST, ...) or your project's
BadRequestException) when resumeId/portfolioId is non-null and
repository.findByIdAndMemberId(...).isEmpty(), and include a clear message like
"resume_id not found or not owned by member" / "portfolio_id not found or not
owned by member" so callers stop processing invalid IDs.

In
`@api/src/test/java/com/samhap/kokomen/global/fixture/interview/ResumeQuestionGenerationFixtureBuilder.java`:
- Around line 46-58: The builder ResumeQuestionGenerationFixtureBuilder.build()
currently allows null for required fields like member and memberResume; either
provide sensible defaults (as other fixture builders do) for member and
memberResume inside the builder or add explicit null-checks (e.g.,
requireNonNull) at the start of build() to fail fast; update the build() method
to validate member and memberResume before constructing ResumeQuestionGeneration
and reference the class/method names
(ResumeQuestionGenerationFixtureBuilder.build, member, memberResume,
ResumeQuestionGeneration) when making the change.

Comment on lines +83 to +95
private MemberResume findMemberResume(Long memberId, Long resumeId) {
if (resumeId == null) {
return null;
}
return memberResumeRepository.findByIdAndMemberId(resumeId, memberId).orElse(null);
}

private MemberPortfolio findMemberPortfolio(Long memberId, Long portfolioId) {
if (portfolioId == null) {
return null;
}
return memberPortfolioRepository.findByIdAndMemberId(portfolioId, memberId).orElse(null);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

잘못된 resume_id/portfolio_id가 조용히 무시됩니다.
ID가 제공됐는데 소유/존재 확인이 실패하면 null로 처리되어 요청이 성공 처리될 수 있습니다. 명시적으로 400을 반환하는 것이 안전합니다.

🔧 수정 제안
-        return memberResumeRepository.findByIdAndMemberId(resumeId, memberId).orElse(null);
+        return memberResumeRepository.findByIdAndMemberId(resumeId, memberId)
+                .orElseThrow(() -> new BadRequestException("존재하지 않는 이력서입니다."));
...
-        return memberPortfolioRepository.findByIdAndMemberId(portfolioId, memberId).orElse(null);
+        return memberPortfolioRepository.findByIdAndMemberId(portfolioId, memberId)
+                .orElseThrow(() -> new BadRequestException("존재하지 않는 포트폴리오입니다."));
🤖 Prompt for AI Agents
In
`@api/src/main/java/com/samhap/kokomen/interview/service/ResumeBasedInterviewService.java`
around lines 83 - 95, The helper methods findMemberResume and
findMemberPortfolio currently return null when an ID is provided but the entity
isn't found, silently allowing success; change them to throw a 400-level
exception instead (e.g., ResponseStatusException(HttpStatus.BAD_REQUEST, ...) or
your project's BadRequestException) when resumeId/portfolioId is non-null and
repository.findByIdAndMemberId(...).isEmpty(), and include a clear message like
"resume_id not found or not owned by member" / "portfolio_id not found or not
owned by member" so callers stop processing invalid IDs.

Comment on lines +46 to +58
public ResumeQuestionGeneration build() {
ResumeQuestionGeneration generation = new ResumeQuestionGeneration(
member,
memberResume,
memberPortfolio,
jobCareer != null ? jobCareer : "신입"
);

if (state == ResumeQuestionGenerationState.COMPLETED) {
generation.complete();
} else if (state == ResumeQuestionGenerationState.FAILED) {
generation.fail();
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

필수 필드 기본값 또는 명시적 검증 추가를 고려해 주세요.
member(또는 memberResume)가 도메인에서 필수라면, 현재 builder는 null로 생성되어 테스트가 애매하게 실패할 수 있습니다. 다른 fixture builder처럼 기본값을 넣거나 requireNonNull로 빠른 실패를 유도하는 편이 안전합니다.

🤖 Prompt for AI Agents
In
`@api/src/test/java/com/samhap/kokomen/global/fixture/interview/ResumeQuestionGenerationFixtureBuilder.java`
around lines 46 - 58, The builder ResumeQuestionGenerationFixtureBuilder.build()
currently allows null for required fields like member and memberResume; either
provide sensible defaults (as other fixture builders do) for member and
memberResume inside the builder or add explicit null-checks (e.g.,
requireNonNull) at the start of build() to fail fast; update the build() method
to validate member and memberResume before constructing ResumeQuestionGeneration
and reference the class/method names
(ResumeQuestionGenerationFixtureBuilder.build, member, memberResume,
ResumeQuestionGeneration) when making the change.

@unifolio0 unifolio0 merged commit 18a6cb9 into develop Jan 20, 2026
5 checks passed
@unifolio0 unifolio0 deleted the feat/#317 branch January 20, 2026 06:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 이력서 기반 면접 목록 이력서 기반 폴더링 API

1 participant