Skip to content

fix: #104 자료 저장 API를 main에 반영 (병합 경로 누락) - #105

Merged
kimwuju merged 10 commits into
mainfrom
feat/materials-api
Oct 19, 2026
Merged

kimwuju merged 10 commits into
mainfrom
feat/materials-api

Conversation

@danjee2009

@danjee2009 danjee2009 commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

무엇을 하나

#104(직무·자료 저장 PUT /api/v1/metaData/materials)의 커밋을 main에 올리고, 이 PR 리뷰에서 나온 문제를 고칩니다.

왜

#103과 #104는 10-17 11:52에 거의 동시에 병합됐습니다.

#104의 기준 브랜치가 main으로 바뀌기 전에 병합돼서, 아래 커밋 6개가 feat/materials-api에만 있고 main에는 없습니다. 지금 main에는 자료 조회(GET)만 있고 저장(PUT)이 없습니다.

fbe9e61 feat: 직무·자료 저장 API(PUT /api/v1/metaData/materials) 추가
984b048 test: 직무·자료 저장 API 테스트 추가
2e3072b fix: 자료 저장 시 파일을 메모리로 읽기 전에 크기 제한 적용
68a71c2 fix: 자료 저장을 임시 파일로 흘려보내고 애매한 실패·비개발 분석 처리 보강
d054d00 fix: 비개발 분석 차단을 모든 분석 경로로 넓히고 늦은 저장 성공을 이어 감
dcc2a7b Merge pull request #104 from Alpha-origin/feat/materials-put

이 6개의 내용 리뷰는 #104에서 끝났습니다.

이 PR에서 새로 고친 것 (리뷰 필요)

PR을 올린 뒤 리뷰에서 나온 문제를 고친 커밋 4개가 더 있습니다. 아직 리뷰받지 않은 코드 변경입니다.

d1a5a21 분석에 첫 문서 대신 포트폴리오 문서를 보냄 (Codex·자동 리뷰 P1)

  • 이력서를 포트폴리오보다 앞에 두면 이력서 URL이 portfolioUrl로 넘어가던 문제를 고쳤습니다.

d7c94c6 모든 분석 경로가 저장된 자료의 포트폴리오를 쓰고, 재시도 뒤 4xx에도 저장 여부를 다시 확인

  • 재분석(POST /api/v1/ai/generate)과 면접 준비 자동 분석(launchIfMissing)이 옛 metaData의 fileUrl 대신 GET /auth/materials에서 PORTFOLIO 문서를 골라 보냅니다. 분석을 시작하는 세 경로가 모두 MaterialsResponse.toAnalysisInput()을 씁니다.
  • 자료 저장 뒤 분석도 인증 서버가 돌려준 저장값을 기준으로 합니다. 저장값에 포트폴리오가 없으면 분석을 요청하지 않고 analysis.status를 failed로 돌려줍니다.
  • 인증 서버 저장 호출은 재시도하므로, 앞 시도가 커밋한 뒤 다음 시도가 4xx를 받을 수 있습니다. 4xx만 보고 올린 파일을 지우지 않고, 다시 조회해 저장 여부를 확인합니다(ae0edb7에서 한 번 더 보강).

dc7df86 S3 일시 장애를 파일 없음과 구분하고, PDF 검사 중 Error·빈 파일 처리 보강

  • 저장된 PDF를 받다가 연결 끊김·S3 5xx가 나면 EXISTING_NOT_FOUND(422) 대신 다시 시도하라는 503을 줍니다. S3에 파일이 없을 때(404, 목록 권한이 없을 때의 403)만 EXISTING_NOT_FOUND입니다.
  • PDF 파서가 스택을 넘치면 BROKEN_PDF로 보고, 검사가 Error로 끝나도 만든 임시 파일을 지웁니다.
  • 0바이트 PDF는 400 대신 그 행에 BROKEN_PDF를 달아 다른 행과 함께 422로 돌려줍니다.

ae0edb7 결과가 불확실한 자료 저장은 올린 파일을 남기고, 비개발 직무의 면접 준비를 막음 (2-dayeon 리뷰 P1·P2)

  • 인증 서버를 부른 뒤의 실패는 어떤 것이든 결과가 확실하지 않습니다. 타임아웃 직후 다시 조회하면 아직 처리 중인 PUT이 커밋되기 전의 옛 값이 보일 수 있어서, 조회에 안 보인다고 파일을 지우면 늦게 커밋된 문서가 지운 파일을 가리킵니다. 이제 다시 조회해 저장돼 있으면 성공으로 이어 가고, 아니면 올린 파일을 지우지 않고 실패를 던집니다. 남긴 파일 주소는 로그에 남깁니다.
  • 비개발 직무로 바꾼 사용자가 면접을 준비하면, 개발 직무 때 남은 분석으로 개발 질문이 준비됐습니다. 면접 준비(prepareInterview)와 준비 재시도(retryPreparation)를 422로 막습니다. 면접 생성은 막지 않으므로 비개발 사용자는 면접을 만들 수는 있고 시작할 때 422를 받습니다.

리뷰어가 봐 주세요

  • 재분석·자동 분석이 이제 GET /auth/materials에 기댑니다. 옛 사용자에게도 이 응답에 GitHub 주소가 들어온다고 가정합니다(MaterialsResponseTest가 그렇게 둡니다). 스테이징에서 옛 사용자로 재분석을 한 번 확인하면 좋겠습니다.
  • 저장이 실패한 것으로 보이는 요청의 새 파일은 이제 S3에 남습니다. 어느 문서도 가리키지 않는 파일을 찾아 지우는 정리 작업은 아직 없습니다.

확인

  • main과 충돌 없음
  • 로컬 ./gradlew test 전체 822개 통과
  • 수정마다 추가한 테스트가 수정 전 코드에서는 실패하고 수정 후 통과하는 것을 확인

이 PR에서 고치지 않은 것

리뷰에서 나왔지만 따로 확인하거나 나중에 정리할 것입니다.

  • 빈 GitHub 칸("")이 저장을 막음 — Web이 빈 칸을 보내는지 확인 필요
  • 인증 서버가 저장 응답에 데이터를 주지 않으면 화면에 빈 자료로 보임 — 계약 C-2 확인 필요
  • 저장이 불확실하게 끝나 남은 S3 파일 정리 — 어느 문서도 가리키지 않는 파일을 찾는 정리 작업 필요
  • 바뀐 것 없이 저장해도 옛 PDF를 다시 받음, 200MB 합계 검사가 요청을 다 받은 뒤에 돎, 저장값 비교 로직·상수 중복 — 나중에 정리

🤖 Generated with Claude Code

kimwuju and others added 6 commits October 17, 2026 16:21
- payload(JSON) + file0~file4 multipart. 저장된 문서는 EXISTING, 새 파일은 NEW
- 검증(직무 → 문서 → GitHub → 보완 입력)을 모아 422 MaterialErrorResponse로 돌려준다
  - 직무 명세 JobRole(01 §3.1), 개발 3직무 사이만 자료 유지, 직무 없는 옛 사용자는 백엔드로 본다
  - PDFBox로 쪽수를 세고 손상·암호화를 가린다. 파일당 50MB, 합계 50쪽, 같은 파일 중복 금지
- 같은 내용이면 저장·분석 없이 analysis: null
- 새 파일을 S3에 올린 뒤 인증 서버 PUT /api/v1/auth/materials로 저장하고, 실패하면 올린 파일을 지운다
- 저장 뒤 지금 /profile 요청 모양(첫 문서 + GitHub)으로 분석을 요청한다. 실패해도 저장은 유지하고 status: failed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- JobRole이 01 §3.1 표와 같은지
- 검증 규칙 20개(06 §3.4 사유별, 직무 변경, 옛 사용자, 모아서 돌려주기)
- 저장 흐름(같은 내용, 업로드·저장·분석 요청, 저장 실패 시 파일 정리, 분석 실패)
- multipart 바인딩과 200·422 응답 모양, 인증 서버 PUT 본문

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 쪽수를 세야 하는 옛 PDF는 S3에서 크기를 먼저 보고 50MB를 넘으면 받지 않는다(FILE_TOO_LARGE)
- 새 파일 합계가 200MB(가정 A-9)를 넘으면 하나도 읽지 않고 documents에 FILE_TOO_LARGE
- 문서 목록이 가리키지 않는 파일 파트가 있으면 400

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 새 파일은 임시 파일로 옮겨 해시·PDFBox·S3 업로드를 파일로 처리한다(요청 하나가 힙에 200MB를 잡지 않게)
- 옛 PDF도 HEAD 뒤 임시 파일로 받고, 받는 동안에도 50MB에서 끊는다
- PDFBox 런타임 예외(ClassCastException 등)도 BROKEN_PDF
- 인증 서버 저장이 5xx·타임아웃이면 다시 조회해 저장된 파일은 남기고, 확인하지 못하면 지우지 않는다(4xx·호출 전 실패만 바로 지움)
- 비개발 직무는 분석 서버 새 요청 모양(C-3) 전까지 분석을 요청하지 않고 status: skipped
- GitHub 주소는 서로 다른 것 10개까지, 같은 저장소는 하나로 합치고, settings·orgs 같은 기능 경로는 거절

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- JobRole.supportsCurrentProfileRequest 하나로 판단: PUT skipped, launchIfMissing 건너뜀, /generate·/dataUpload 422(dataUpload 는 저장 전에)
- 인증 서버 응답이 실패여도 다시 조회한 값이 보낸 내용과 같으면 저장 성공으로 보고 분석 요청까지 이어 간다
- S3 에서 받는 도중 상한을 넘으면 닫기 전에 abort 해 남은 본문을 읽지 않는다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T10:55:51.115648Z dcc2a7b PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: dcc2a7b750

ℹ️ 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".

@danjee2009 danjee2009 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

확인된 문제

  • [P1] src/main/java/repit/repit_api_server/domain/metadata/service/MaterialsService.java:152 — 분석에 포트폴리오 대신 첫 번째 문서를 보냅니다. 개발 직군 문서 검증은 필수 PORTFOLIO가 목록 어디에든 있으면 통과하고 문서 순서도 유지합니다. 따라서 사용자가 선택 문서에 RESUME을 PORTFOLIO보다 먼저 두면 이 코드가 이력서 URL을 portfolioUrl로 전달해 잘못된 종합 분석을 생성합니다. 첫 문서 인덱스에 의존하지 말고 PORTFOLIO 타입의 문서를 찾아 전달해 주세요.

PR 설명과 전체 변경 diff, 자료 검증·저장·분석 요청 흐름 및 관련 테스트를 확인했습니다. 별도 테스트 실행과 실제 S3·인증·분석 서버 연동 검증은 수행하지 않았습니다.

개발 직무 검증은 PORTFOLIO가 목록 어디에 있어도 통과하고 순서도 그대로 둔다.
이력서를 포트폴리오보다 앞에 두면 이력서 URL이 portfolioUrl로 넘어가
잘못된 분석이 나왔다. 문서 종류로 PORTFOLIO를 찾아 보낸다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kimwuju

kimwuju commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

리뷰에서 짚어 주신 [P1] MaterialsService.java:152 — 분석에 포트폴리오 대신 첫 번째 문서를 보냄 건을 d1a5a21에서 반영했습니다.

  • 첫 문서 인덱스에 기대지 않고 PORTFOLIO 유형 문서를 찾아 portfolioUrl로 보냅니다. 저장되는 문서 순서는 요청 그대로 둡니다.
  • 이력서를 포트폴리오보다 앞에 둔 경우를 테스트로 추가했습니다(수정 전 실패 → 수정 후 통과).
  • ./gradlew test 전체 805개 통과.

kimwuju and others added 2 commits October 19, 2026 11:53
- 재분석(POST /ai/generate)과 면접 준비 자동 분석(launchIfMissing)이 옛 metaData의
  fileUrl 대신 저장된 자료에서 PORTFOLIO 문서를 골라 보낸다. 자료 저장 뒤 분석도
  같은 MaterialsResponse.toAnalysisInput()을 쓴다.
- 자료 저장 호출은 재시도하므로 앞 시도가 커밋한 뒤 다음 시도가 4xx를 받을 수 있다.
  4xx만 보고 올린 파일을 지우지 않고, 다시 조회해 저장 여부를 확인한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 저장된 PDF를 받다가 연결 끊김·S3 5xx가 나면 EXISTING_NOT_FOUND(422) 대신
  다시 시도하라는 503을 던진다. S3에 파일이 없을 때(404, 목록 권한이 없을 때의 403)만
  NOT_FOUND로 본다.
- PDF 파서가 스택을 넘치면 손상(BROKEN_PDF)으로 보고, 검사가 Error로 끝나도
  만든 임시 파일을 지운다.
- 0바이트 PDF는 400 대신 그 행에 BROKEN_PDF를 달아 다른 행과 함께 422로 돌려준다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@2-dayeon

Copy link
Copy Markdown
Contributor

[P1] 타임아웃 후 실제 저장될 파일을 삭제할 수 있습니다.
MaterialsService.java:120
PUT 타임아웃 직후 GET이 이전 데이터를 반환하면 새 파일을 삭제합니다. 하지만 인증 서버의 PUT이 아직 처리 중이었다면, 이후 저장이 완료되어 삭제된 파일 URL이 DB에 남습니다. GET에서 안 보인다는 이유만으로 저장 실패를 확정하면 안 됩니다. 결과가 불확실한 요청의 파일은 보존하고, 나중에 미참조 여부를 확인해 정리해야 합니다.

@2-dayeon

Copy link
Copy Markdown
Contributor

[P2] 비개발 직무로 변경해도 이전 개발 직무의 질문으로 면접이 준비됩니다.
AnalysisLaunchService.java:101–102
비개발 직무일 때 분석만 건너뛰고 반환하므로, 호출자는 그대로 면접 준비를 계속합니다. 기존 BACKEND 분석이 있는 사용자가 PRODUCT_MANAGER로 변경하면 질문 풀이 이전 개발 질문을 사용합니다. 비개발 직무의 새 면접 준비를 명시적으로 차단하거나, 현재 직무와 분석 결과의 일치 여부를 검증해야 합니다.

- 인증 서버를 부른 뒤의 실패는 어떤 것이든 결과가 확실하지 않다. 타임아웃 직후 다시
  조회하면 아직 처리 중인 PUT이 커밋되기 전의 옛 값이 보일 수 있어, 조회에 안 보인다고
  올린 파일을 지우면 늦게 커밋된 문서가 지운 파일을 가리킨다. 다시 조회해 저장돼 있으면
  성공으로 이어 가고, 아니면 파일을 남긴 채 실패를 던진다(정리용으로 주소를 로그에 남긴다).
- 비개발 직무로 바꾼 사용자가 면접을 준비하면 개발 직무 때 남은 분석으로 개발 질문이
  준비됐다. 면접 준비와 준비 재시도를 422로 막는다(분석 서버 새 요청 모양 C-3 전).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kimwuju
kimwuju merged commit be77bb4 into main Oct 19, 2026
1 check passed
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.

3 participants