Revert "[feature] 동아리 지원서 폼 제작 시에 학기를 선택할 수 있고 생성된 모든 지원서 폼을 학기별로 분류하여 조회할 수 있다"#750
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Change Summary |
|---|---|
Controller/API 통합backend/src/main/java/moadong/club/controller/ClubApplyController.java |
엔드포인트/메서드/DTO 명칭 “Form” → “Application/Question”로 변경. applicationFormId 경로변수 제거. 학기 관련 GET 제거. POST/PUT/GET 경로 단순화 및 배치 편집/삭제로 통일. |
Service 전면 리팩터링backend/src/main/java/moadong/club/service/ClubApplyService.java |
도메인 전환(ClubApplicationForm → ClubQuestion/ClubApplication). 생성/수정/조회/지원/지원자 편집·삭제 로직 재구성. 새 리포지토리 사용, 권한/검증 로직 갱신, 응답 DTO 변경. |
엔티티 리네이밍/스키마 변경.../entity/ClubApplication.java, .../entity/ClubApplicationQuestion.java, .../entity/ClubQuestion.java |
ClubApplicant → ClubApplication, ClubApplicationFormQuestion → ClubApplicationQuestion, ClubApplicationForm → ClubQuestion로 변경. 콜렉션명 변경, formId → questionId. 학기 필드/메서드 삭제. 관련 메서드 시그니처 갱신. |
Enums 변경.../enums/ApplicationStatus.java, .../enums/SemesterTerm.java |
ApplicantStatus → ApplicationStatus로 리네임. SemesterTerm enum 삭제. |
요청 DTO 갱신.../payload/request/ClubApplicantEditRequest.java, .../payload/request/ClubApplicationCreateRequest.java, .../payload/request/ClubApplicationEditRequest.java |
상태 타입 ApplicationStatus로 교체. Create/Edit 요청 레코드명 교체 및 학기 필드/검증 제거, 질문 리스트 검증 단순화. |
응답/결과 DTO 정리.../payload/dto/ClubApplicantsResult.java, .../payload/response/ClubApplicationResponse.java |
ClubApplicantsResult가 ClubApplication/ApplicationStatus 사용. 신규 ClubApplicationResponse 추가(title/description/questions). |
학기/폼 관련 DTO 제거.../payload/dto/ClubApplicationFormSlim.java, .../payload/dto/ClubApplicationFormsResult*.java, .../payload/response/ClubApplicationForm[s]Response.java, .../payload/response/SemesterOptionResponse.java |
학기 및 폼 리스트 관련 공개 타입 일체 삭제. |
리포지토리 교체.../repository/ClubApplicationRepository.java, .../repository/ClubQuestionRepository.java |
신규 저장소 추가: Application 질의( questionId 기반 ), Question 질의( clubId 단건 ). |
리포지토리 제거.../repository/ClubApplicantsRepository.java, .../repository/ClubApplicationFormsRepository*.java |
Applicant/Form 관련 MongoRepository 및 커스텀 Aggregation 저장소 삭제. |
예외/핸들러 소소한 변경.../global/exception/ErrorCode.java, .../global/exception/GlobalExceptionHandler.java |
메시지 문구 수정(“지원서 양식” → “지원서”). 핸들러 포매팅 변경(기능 변화 없음). |
테스트/픽스처 정리.../test/java/moadong/club/service/ClubApplyServiceTest.java, .../test/java/moadong/fixture/ClubApplicationEditFixture.java |
Form 기반에서 Question 기반으로 의존성/ID/저장소 전환. 픽스처와 테스트 로직을 새 API에 맞게 갱신. |
Sequence Diagram(s)
sequenceDiagram
autonumber
actor Client
participant Controller as ClubApplyController
participant Service as ClubApplyService
participant QRepo as ClubQuestionRepository
participant ARepo as ClubApplicationRepository
participant DB as MongoDB
rect rgb(240,248,255)
note over Client,Service: 지원서(질문지) 생성/수정/조회 (clubId 기준, applicationFormId 제거)
Client->>Controller: POST /clubs/{clubId}/application (CreateRequest)
Controller->>Service: createClubApplication(clubId, user, req)
Service->>QRepo: save/update ClubQuestion
QRepo-->>Service: persisted
Service-->>Controller: 200 OK
Client->>Controller: PUT /clubs/{clubId}/application (EditRequest)
Controller->>Service: editClubApplication(clubId, user, req)
Service->>QRepo: update questions
QRepo-->>Service: ok
Service-->>Controller: 200 OK
Client->>Controller: GET /clubs/{clubId}/apply
Controller->>Service: getClubApplication(clubId)
Service->>QRepo: findByClubId
QRepo-->>Service: ClubQuestion
Service-->>Controller: ClubApplicationResponse
end
rect rgb(245,255,245)
note over Client,ARepo: 동아리 지원 제출 (questionId 내부적으로 사용)
Client->>Controller: POST /clubs/{clubId}/apply (ApplyRequest)
Controller->>Service: applyToClub(clubId, req)
Service->>QRepo: load ClubQuestion
QRepo-->>Service: questions
Service->>ARepo: save ClubApplication (answers)
ARepo-->>Service: persisted
Service-->>Controller: 200 OK
end
rect rgb(255,250,240)
note over Client,Service: 지원자 관리 배치 편집/삭제 (경로변수 appId 제거)
Client->>Controller: PUT /clubs/{clubId}/applicant (List<EditRequest>)
Controller->>Service: editApplicantDetail(clubId, list, user)
Service->>ARepo: findAllByIdInAndQuestionId(...)
ARepo-->>Service: applications
Service->>ARepo: bulk update status/memo
ARepo-->>Service: ok
Service-->>Controller: 200 OK
Client->>Controller: DELETE /clubs/{clubId}/applicant (DeleteRequest)
Controller->>Service: deleteApplicant(clubId, req, user)
Service->>ARepo: findAllByIdInAndQuestionId(...)
ARepo-->>Service: applications
Service->>ARepo: deleteAll
ARepo-->>Service: ok
Service-->>Controller: 200 OK
end
rect rgb(253,245,255)
note over Client,Service: 지원 현황 조회
Client->>Controller: GET /clubs/{clubId}/apply/info
Controller->>Service: getClubApplyInfo(clubId, user)
Service->>QRepo: findByClubId
Service->>ARepo: findAllByQuestionId
ARepo-->>Service: applications
Service-->>Controller: ClubApplyInfoResponse
end
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
- [release] 모아동 BE v1.0.4 배포 #579: 동일한 컨트롤러/서비스/엔티티/리포지토리 전환(Form → Question/Application) 작업과 겹치므로 직접적으로 관련.
- [feature] 동아리 지원서 폼 제작 시에 학기를 선택할 수 있고 생성된 모든 지원서 폼을 학기별로 분류하여 조회할 수 있다 #739: 동일 영역 수정이나
applicationFormId·학기 기반 모델을 유지하는 변경과 충돌 가능성이 있어 코드 수준에서 상충 관계. - [release] 모아동 BE ver 1.0.3 #503: ClubApplyController/Service, ApplicationStatus, 저장소/DTO 리네이밍을 함께 다뤄 본 PR과 범위가 중첩.
Suggested labels
🔨 Refactor, 📬 API, 💾 BE
Suggested reviewers
- Zepelown
- lepitaaar
- PororoAndFriends
- oesnuj
Pre-merge checks and finishing touches
❌ Failed checks (3 warnings)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Linked Issues Check | 이 PR은 지원서 폼 생성 시 학기 선택 기능을 제거하여 MOA-231 링크 이슈의 요구사항인 학기 선택 기능 추가 요구와 정면으로 상충하며, 요구사항을 충족하지 않습니다. | MOA-231에 따라 학기 선택 기능을 복원하거나 별도의 PR로 리버트 작업을 분리해야 합니다. | |
| Out of Scope Changes Check | 이 PR은 학기 관련 코드 제거 외에도 엔티티, 컨트롤러, DTO 및 리포지토리 인터페이스 대규모 리네이밍 등 MOA-231 이슈 범위와 무관한 광범위한 리팩토링을 포함하고 있습니다. | 학기 선택 코드 리버트만 포함하도록 범위를 제한하거나 리팩토링 및 명명 변경은 별도 PR로 분리하세요. | |
| Docstring Coverage | Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title Check | ✅ Passed | 제목은 이전 기능 구현을 되돌린다는 주된 작업을 명확히 나타내며, 되돌릴 대상 기능을 구체적으로 언급하고 있어 PR의 의도를 정확히 설명합니다. |
✨ Finishing touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
revert-739-feature/#732-applicaton-semester-selection-MOA-231
Tip
👮 Agentic pre-merge checks are now available in preview!
Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
- Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
- Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.
Please see the documentation for more information.
Example:
reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post.
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 @coderabbitai help to get the list of available commands and usage tips.
Test Results72 tests 69 ✅ 7s ⏱️ Results for commit 7d8573e. |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
backend/src/main/java/moadong/global/exception/ErrorCode.java (1)
40-48: APPLICATION_NOT_FOUND와 APPLICANT_NOT_FOUND 메시지 중복 — 정리 필요두 상수가 동일한 한글 메시지("지원서가 존재하지 않습니다.")를 사용 중이며 사용처는 backend/src/main/java/moadong/club/service/ClubApplyService.java: APPLICATION_NOT_FOUND(77, 90, 126), APPLICANT_NOT_FOUND(184, 208)입니다.
- 옵션 A (간단, 하위호환 유지): APPLICANT_NOT_FOUND 메시지를 "지원자가 존재하지 않습니다."로 수정
- APPLICANT_NOT_FOUND(HttpStatus.NOT_FOUND, "900-2", "지원서가 존재하지 않습니다."), + APPLICANT_NOT_FOUND(HttpStatus.NOT_FOUND, "900-2", "지원자가 존재하지 않습니다."),
- 옵션 B (정리 후 제거): APPLICANT_NOT_FOUND 사용처를 APPLICATION_NOT_FOUND(또는 의미에 맞는 새 코드)로 교체한 뒤 중복 항목 제거
backend/src/main/java/moadong/club/service/ClubApplyService.java (1)
88-115: Answer.value에 대한 빈 문자열(공백) 검증 추가 필요ClubApplyRequest.Answer.value에는 현재 @NotNull만 적용되어 있어 빈 문자열/공백은 통과됩니다. cipher.encrypt(answer.value()) 호출 전에 @notblank를 추가하거나 서비스에서 value.isBlank() 검사 후 예외를 던지세요. 위치: backend/src/main/java/moadong/club/payload/request/ClubApplyRequest.java (record Answer의 value 필드)
🧹 Nitpick comments (16)
backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java (1)
15-36: Mongo 도메인에서 JPA 어노테이션 제거 및 문자열 필드 검증 강화 제안
@Enumerated(EnumType.STRING)은 JPA용이며 Spring Data MongoDB에는 의미가 없습니다. 제거하는 편이 명확합니다.title,description은@NotNull대신@NotBlank가 적절합니다.- @NotNull - private String title; + @NotBlank + private String title; - @NotNull - private String description; + @NotBlank + private String description; - @Enumerated(EnumType.STRING) - @NotNull - private ClubApplicationQuestionType type; + @NotNull + private ClubApplicationQuestionType type;backend/src/main/java/moadong/club/entity/ClubQuestion.java (2)
48-54: 메서드 네이밍 잔존 “Form” 용어 — 도메인 리네이밍과 불일치
updateFormTitle,updateFormDescription은 현재 도메인(Question)과 용어가 맞지 않습니다.updateTitle,updateDescription으로 정리 제안. 영향 범위가 넓다면 별도 리팩터링 이슈로 분리해도 됩니다.
56-59: self-assign 리스트 전달 시 데이터 소거 가능성
newQuestions가this.questions와 동일 인스턴스인 경우clear()후addAll()이 no-op이 되어 항목이 전부 사라질 수 있습니다. 동일 참조 방지 가드 추가 권장.- public void updateQuestions(List<ClubApplicationQuestion> newQuestions) { - this.questions.clear(); - this.questions.addAll(newQuestions); - } + public void updateQuestions(List<ClubApplicationQuestion> newQuestions) { + if (newQuestions == null) { + this.questions.clear(); + return; + } + if (this.questions == newQuestions) { + return; + } + this.questions.clear(); + this.questions.addAll(newQuestions); + }backend/src/main/java/moadong/club/payload/response/ClubApplicationResponse.java (1)
8-13: 응답 DTO에서 엔티티(ClubApplicationQuestion) 직접 노출 지양엔티티를 외부 응답에 노출하면 필드 추가/내부 변경이 API 호환성에 바로 영향을 줍니다. 전용 Response DTO로 분리하고 매핑하는 편이 안전합니다.
-public record ClubApplicationResponse( +public record ClubApplicationResponse( String title, String description, - List<ClubApplicationQuestion> questions + List<ClubApplicationQuestionResponse> questions ) { }추가로, 서비스 단(ClubApplyService.getClubApplication)에서 엔티티 → DTO 매핑을 수행하세요(예: stream map).
backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java (1)
15-15: 파라미터 명 혼동 최소화questionId로 필터링하면서 매개변수명이 clubId입니다. 혼동을 줄이기 위해 이름을 questionId로 통일하세요(기능 동일).
- List<ClubApplication> findAllByIdInAndQuestionId(List<String> ids, String clubId); + List<ClubApplication> findAllByIdInAndQuestionId(List<String> ids, String questionId);backend/src/main/java/moadong/club/service/ClubApplyService.java (5)
154-165: Null 안전성: app.getAnswers()가 null인 기존 데이터 방어과거 데이터에 answers가 없으면 NPE가 발생합니다. 빈 리스트로 처리하도록 방어 로직을 추가하세요.
private ClubApplication sortApplicationAnswers(ClubQuestion application, ClubApplication app) { - Map<Long, ClubQuestionAnswer> answerMap = app.getAnswers().stream() + List<ClubQuestionAnswer> source = Optional.ofNullable(app.getAnswers()).orElseGet(List::of); + Map<Long, ClubQuestionAnswer> answerMap = source.stream() .collect(Collectors.toMap(ClubQuestionAnswer::getId, answer -> answer));
167-195: 변수 네이밍 복수형 추천 및 빈 요청 최적화List<...> application → applications로 변경하면 가독성이 좋아집니다. 또한 요청이 빈 배열이면 조기 반환이 가능해 불필요한 DB 호출을 줄일 수 있습니다.
-List<ClubApplication> application = clubApplicationRepository.findAllByIdInAndQuestionId(applicationIds, clubId); -if (application.size() != applicationIds.size()) { +if (applicationIds.isEmpty()) return; +List<ClubApplication> applications = clubApplicationRepository.findAllByIdInAndQuestionId(applicationIds, clubId); +if (applications.size() != applicationIds.size()) { throw new RestApiException(ErrorCode.APPLICANT_NOT_FOUND); } -application.forEach(app -> { +applications.forEach(app -> { ClubApplicantEditRequest editRequest = requestMap.get(app.getId()); app.updateMemo(editRequest.memo()); app.updateStatus(editRequest.status()); }); -clubApplicationRepository.saveAll(application); +clubApplicationRepository.saveAll(applications);
214-241: 중복 답변 ID 및 값 검증 보강
- 동일 질문에 대한 중복 Answer ID가 오면 현재는 마지막 값만 유효합니다. 중복을 명시적으로 차단하세요.
- SHORT/LONG 외 타입일 때 길이 검증 제외는 의도라면 OK, 아니라면 타입별 추가 검증 고려.
Set<Long> answerIds = answers.stream() .map(ClubApplyRequest.Answer::id) .collect(Collectors.toSet()); +if (answerIds.size() != answers.size()) { + throw new RestApiException(ErrorCode.DUPLICATE_ANSWER); +}
259-293: createQuestions/updateQuestions 중복 로직 통합두 메서드가 거의 동일합니다. 질문 빌드 로직을 헬퍼로 추출해 유지보수성을 높이세요.
// 헬퍼 private List<ClubApplicationQuestion> buildQuestions(List<ClubApplyQuestion> reqs) { List<ClubApplicationQuestion> newQuestions = new ArrayList<>(); for (var question : reqs) { List<ClubQuestionItem> items = question.items().stream() .map(it -> ClubQuestionItem.builder().value(it.value()).build()) .toList(); ClubQuestionOption options = ClubQuestionOption.builder() .required(question.options().required()) .build(); newQuestions.add(ClubApplicationQuestion.builder() .id(question.id()) .title(question.title()) .description(question.description()) .type(question.type()) .options(options) .items(items) .build()); } return newQuestions; }적용:
-List<ClubApplicationQuestion> newQuestions = new ArrayList<>(); -... (loop) -clubQuestion.updateQuestions(newQuestions); +clubQuestion.updateQuestions(buildQuestions(request.questions()));Also applies to: 297-330
332-343: getClubQuestion의 생성 fallback은 create 전용으로 한정 권장현재는 모든 호출에서 미존재 시 새 객체를 반환합니다. edit/단건 조회 경로에서는 존재하지 않으면 예외를 던지고, create 경로에서만 fallback을 쓰도록 분리하세요.
backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java (1)
9-21: ClubApplyQuestion 존재 확인 — questions에 최소 1개 제약(@SiZe) 추가 권장backend/src/main/java/moadong/club/payload/request/ClubApplyQuestion.java가 존재하므로 import/컴파일 오류 없음. 폼에서 '최소 1문항'을 요구하면 backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java의 questions에 @SiZe(min = 1) 추가하세요.
backend/src/main/java/moadong/club/payload/request/ClubApplicantEditRequest.java (1)
6-6: ClubApplication.updateStatus 매핑 확인 및 memo 검증backend/src/main/java/moadong/club/controller/ClubApplyController.java (4)
39-48: 생성 API 응답 스펙 개선 제안 (201/Location 또는 생성 리소스 반환)현재 단순 메시지(200 OK)만 반환합니다. 생성된 리소스 식별자/본문 또는 Location 헤더(201 Created)로의 정합성을 권장합니다. OpenAPI 문서화도 좋아집니다.
50-59: 수정 API도 결과 본문/표준화된 응답을 고려하세요수정 후 최신 상태(또는 ETag) 반환을 권장합니다. 최소한 성공 메시지 일관성(i18n 포함)을 맞춰 주세요.
67-73: 지원 API의 인증 요건 확인 필요현 상태는 비인증 사용자도 지원 가능하게 보입니다. 의도라면 OK, 아니라면 인증이 필요합니다. 아래와 같이 보강 가능합니다.
@PostMapping("/apply") - @Operation(summary = "클럽 지원", description = "클럽에 지원합니다") + @Operation(summary = "클럽 지원", description = "클럽에 지원합니다") + @PreAuthorize("isAuthenticated()") + @SecurityRequirement(name = "BearerAuth") public ResponseEntity<?> applyToClub(@PathVariable String clubId, @RequestBody @Validated ClubApplyRequest request) { clubApplyService.applyToClub(clubId, request); return Response.ok("success apply"); }
84-96: 배열 요소 유효성 검증 강화 및 배치 수정 트랜잭션 확인리스트 자체의
@NotEmpty는 좋습니다. 요소 단위 검증을 확실히 하려면 타입 사용 위치에@Valid를 권장합니다. 또한 대량 수정은 서비스 단에서 하나의 트랜잭션으로 처리되는지 확인 부탁드립니다.- @RequestBody @Valid @NotEmpty List<ClubApplicantEditRequest> request, + @RequestBody @NotEmpty List<@Valid ClubApplicantEditRequest> request,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (27)
backend/src/main/java/moadong/club/controller/ClubApplyController.java(2 hunks)backend/src/main/java/moadong/club/entity/ClubApplication.java(2 hunks)backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java(1 hunks)backend/src/main/java/moadong/club/entity/ClubQuestion.java(3 hunks)backend/src/main/java/moadong/club/enums/ApplicationStatus.java(1 hunks)backend/src/main/java/moadong/club/enums/SemesterTerm.java(0 hunks)backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java(2 hunks)backend/src/main/java/moadong/club/payload/dto/ClubApplicationFormSlim.java(0 hunks)backend/src/main/java/moadong/club/payload/dto/ClubApplicationFormsResult.java(0 hunks)backend/src/main/java/moadong/club/payload/dto/ClubApplicationFormsResultItem.java(0 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplicantEditRequest.java(2 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplicationCreateRequest.java(1 hunks)backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java(2 hunks)backend/src/main/java/moadong/club/payload/response/ClubApplicationFormResponse.java(0 hunks)backend/src/main/java/moadong/club/payload/response/ClubApplicationFormsResponse.java(0 hunks)backend/src/main/java/moadong/club/payload/response/ClubApplicationResponse.java(1 hunks)backend/src/main/java/moadong/club/payload/response/SemesterOptionResponse.java(0 hunks)backend/src/main/java/moadong/club/repository/ClubApplicantsRepository.java(0 hunks)backend/src/main/java/moadong/club/repository/ClubApplicationFormsRepository.java(0 hunks)backend/src/main/java/moadong/club/repository/ClubApplicationFormsRepositoryCustom.java(0 hunks)backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java(1 hunks)backend/src/main/java/moadong/club/repository/ClubQuestionRepository.java(1 hunks)backend/src/main/java/moadong/club/service/ClubApplyService.java(11 hunks)backend/src/main/java/moadong/global/exception/ErrorCode.java(1 hunks)backend/src/main/java/moadong/global/exception/GlobalExceptionHandler.java(2 hunks)backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java(4 hunks)backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java(2 hunks)
💤 Files with no reviewable changes (10)
- backend/src/main/java/moadong/club/payload/dto/ClubApplicationFormSlim.java
- backend/src/main/java/moadong/club/payload/response/ClubApplicationFormsResponse.java
- backend/src/main/java/moadong/club/payload/dto/ClubApplicationFormsResultItem.java
- backend/src/main/java/moadong/club/repository/ClubApplicationFormsRepository.java
- backend/src/main/java/moadong/club/payload/response/SemesterOptionResponse.java
- backend/src/main/java/moadong/club/repository/ClubApplicantsRepository.java
- backend/src/main/java/moadong/club/payload/dto/ClubApplicationFormsResult.java
- backend/src/main/java/moadong/club/payload/response/ClubApplicationFormResponse.java
- backend/src/main/java/moadong/club/repository/ClubApplicationFormsRepositoryCustom.java
- backend/src/main/java/moadong/club/enums/SemesterTerm.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
backend/src/main/java/moadong/club/payload/request/ClubApplicationCreateRequest.javabackend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.javabackend/src/main/java/moadong/club/entity/ClubApplication.javabackend/src/test/java/moadong/fixture/ClubApplicationEditFixture.javabackend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.javabackend/src/main/java/moadong/club/service/ClubApplyService.javabackend/src/test/java/moadong/club/service/ClubApplyServiceTest.javabackend/src/main/java/moadong/club/controller/ClubApplyController.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
PR: Moadong/moadong#703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.
Applied to files:
backend/src/main/java/moadong/club/entity/ClubApplication.javabackend/src/main/java/moadong/club/entity/ClubApplicationQuestion.javabackend/src/main/java/moadong/club/repository/ClubApplicationRepository.javabackend/src/main/java/moadong/club/repository/ClubQuestionRepository.javabackend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.javabackend/src/main/java/moadong/club/entity/ClubQuestion.javabackend/src/main/java/moadong/club/service/ClubApplyService.javabackend/src/test/java/moadong/club/service/ClubApplyServiceTest.javabackend/src/main/java/moadong/club/controller/ClubApplyController.java
🧬 Code graph analysis (4)
backend/src/main/java/moadong/club/entity/ClubApplication.java (2)
backend/src/main/java/moadong/club/entity/ClubQuestion.java (1)
Document(17-69)backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java (1)
AllArgsConstructor(12-36)
backend/src/main/java/moadong/club/entity/ClubQuestion.java (3)
backend/src/main/java/moadong/club/entity/ClubApplication.java (1)
Document(17-53)backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java (1)
AllArgsConstructor(12-36)backend/src/main/java/moadong/club/entity/ClubQuestionAnswer.java (1)
AllArgsConstructor(7-16)
backend/src/main/java/moadong/club/service/ClubApplyService.java (2)
backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java (1)
AllArgsConstructor(12-36)backend/src/main/java/moadong/club/entity/ClubQuestionAnswer.java (1)
AllArgsConstructor(7-16)
backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java (1)
backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java (1)
ClubApplicationEditFixture(6-23)
🔇 Additional comments (9)
backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java (1)
24-46: AES 복호화 예외 처리 흐름 및 응답 구성 LGTM예외 시 표준
ErrorCode.AES_CIPHER_ERROR로 래핑하고, 응답에 복호화된 값만 담는 흐름이 명확합니다.backend/src/main/java/moadong/global/exception/GlobalExceptionHandler.java (1)
24-26: 포매팅 정리만 수행 — 동작 영향 없음체이닝 정렬로 가독성이 좋아졌습니다. 동작 변화 없습니다.
Also applies to: 44-46
backend/src/main/java/moadong/club/enums/ApplicationStatus.java (1)
3-8: 하위 호환성 수동 확인 필요 — DECLINED 추가
- backend/src/main/java/moadong/club/enums/ApplicationStatus.java (lines 3–8)에 DECLINED 추가됨. 자동 검색(rg)으로 ApplicationStatus.DECLINED 또는 REJECTED 참조를 찾지 못해 영향 범위 확인 불가.
- 자동 검색은 enum 상수 참조만 확인했음 — DB에 저장된 문자열 리터럴, 외부 클라이언트(프론트/모바일) 또는 SQL 쿼리에서 사용 중인 값은 별도 수동 점검 필요.
- 배포 전 확인 항목: DB에 저장된 상태값 및 마이그레이션 필요성, JPA/직렬화 매핑(EnumType.STRING, Jackson 등), API/클라이언트 계약(문자열 기대값), 기존 쿼리·인덱스 영향.
- 한글 주석 “불합” → “불합격”으로 정제 권장.
backend/src/main/java/moadong/club/payload/request/ClubApplicationCreateRequest.java (1)
18-21: 오류 — ClubApplyQuestion DTO가 존재하므로 타입 변경 불필요backend/src/main/java/moadong/club/payload/request/ClubApplyQuestion.java에 public record ClubApplyQuestion(...)가 정의되어 있어 List는 유효합니다. 따라서 원래 제안한 ClubApplicationQuestion(엔티티)으로의 타입 변경 및 엔티티 import는 잘못된 권고입니다.
권장 조치:
- CreateRequest / EditRequest의 questions 필드에 최소 1개 보장을 위해 @SiZe(min = 1) 추가.
위치: backend/src/main/java/moadong/club/payload/request/ClubApplicationCreateRequest.java:20, backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java:20권장 diff:
@@ - @NotNull - @Valid - List<ClubApplyQuestion> questions + @NotNull + @Valid + @Size(min = 1) + List<ClubApplyQuestion> questions추가(import): jakarta.validation.constraints.Size 또는 프로젝트에 맞게 javax.validation.constraints.Size 선택.
Likely an incorrect or invalid review comment.
backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java (1)
55-106: 잘못된 지적 — ClubQuestion 엔티티에 이미 @Version(낙관적 락)이 적용되어 있습니다.
backend/src/main/java/moadong/club/entity/ClubQuestion.java:45에 @Version 어노테이션이 존재합니다. 따라서 원래 리뷰 코멘트는 부정확합니다. 테스트에서는 실제로 발생하는 예외 타입을 구체적으로 검증하세요.Likely an incorrect or invalid review comment.
backend/src/main/java/moadong/club/payload/response/ClubApplicationResponse.java (1)
8-9: Lombok 버전 명시 필요 — record + @builder는 Lombok ≥ 1.18.20 필요build.gradle*에서 'org.projectlombok:lombok'이 버전 없이 참조(compileOnly/annotationProcessor)되고 있습니다. Lombok의 record/@builder 지원은 v1.18.20에서 도입되었으니, 버전 선언 위치(gradle.properties, dependencyManagement/BOM, parent POM, buildSrc 등)를 확인해 명시적으로 ≥1.18.20으로 고정하거나 최신 안정 버전으로 업데이트하고 빌드 및 IDE(플러그인)에서 정상 동작하는지 검증하세요.
backend/src/main/java/moadong/club/controller/ClubApplyController.java (3)
61-65: 경로 의미/호환성 재확인: GET /apply 가 “지원서 템플릿” 조회 맞나요?운영 프론트와의 호환성 때문에 리버트하는 PR이라면, 기존 프론트가 기대하던 경로/스키마로 복구됐는지 확인이 필요합니다. 또한 본 메서드가
ResponseEntity를 서비스에서 직접 받아 반환하는 계약이 일관적인지도 점검 바랍니다. 참고: clubId만으로 조회하는 설계는 과거 합의(QuestionId ≡ clubId)와 부합합니다.
75-82: LGTM: 관리자용 현황 API 보안 어노테이션 적절
@PreAuthorize("isAuthenticated()")및SecurityRequirement설정이 타 엔드포인트와 일관적입니다.
98-109: 컨트롤러·서비스 메서드명 불일치 경고는 부정확 — 응답 메시지 i18n 정리는 권장
- 서비스에 deleteApplicant(String, ClubApplicantDeleteRequest, CustomUserDetails) 구현이 존재하고 컨트롤러 removeApplicant에서 해당 메서드를 호출하므로 컴파일 오류 없음 (확인: backend/src/main/java/moadong/club/service/ClubApplyService.java:197 → backend/src/main/java/moadong/club/controller/ClubApplyController.java:104,107).
- 응답 문자열("success delete applicant")은 한·영 혼용이므로 i18n 리소스 사용 또는 한국어 메시지("지원자 삭제 성공")로 정리 권장 — 위치: backend/src/main/java/moadong/club/controller/ClubApplyController.java (해당 메서드의 return 문).
Likely an incorrect or invalid review comment.
| import moadong.club.payload.request.ClubApplicationCreateRequest; | ||
| import moadong.club.payload.request.ClubApplicationEditRequest; |
There was a problem hiding this comment.
🧩 Analysis chain
리버트(PR #739) 의도와 DTO 네이밍/엔드포인트가 상충합니다
이 컨트롤러는 ClubApplication* DTO와 /application·/apply 경로를 사용합니다. 리버트라면 기존 *Form* 기반 시그니처/경로로 되돌아가야 할 가능성이 큽니다. 실제로 전역적으로 무엇이 남아있는지 확인 부탁드립니다. 불일치라면 코드 원복 또는 PR 제목/설명 정정이 필요합니다.
다음 스크립트로 서비스/DTO/매핑 잔존 여부를 점검하세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== ClubApplyService 위치 =="
fd -t f -p 'ClubApplyService.java' || true
rg -nP --type=java 'class\s+ClubApplyService\b|interface\s+ClubApplyService\b' -C1 || true
echo "== 서비스 메서드 시그니처 스캔 =="
rg -nP --type=java '\b(createClubApplication(Form)?|editClubApplication(Form)?|getClubApplication(Form)?|getClubApplyInfo|editApplicantDetail|deleteApplicant|removeApplicant)\s*\(' || true
echo "== DTO 클래스/참조 스캔 =="
rg -nP --type=java 'class\s+ClubApplication(Form)?(Create|Edit)Request\b' || true
rg -nP --type=java '\bClubApplication(Form)?(Create|Edit)Request\b' -g '!**/build/**' || true
echo "== 컨트롤러 매핑 스캔 =="
rg -nP --type=java -C1 '@(Post|Put|Get|Delete)Mapping\("(/application|/apply|/application-form)"' || trueLength of output: 4518
리버트 의도와 DTO/엔드포인트 불일치 — 수정 필요
- 요지: 현재 코드베이스가 ClubApplication* DTO와 /application, /apply 엔드포인트를 사용하고 있어 '리버트( Form 네이밍/경로 복원 )' 의도와 상충합니다.
- 근거:
- backend/src/main/java/moadong/club/service/ClubApplyService.java: createClubApplication(..., ClubApplicationCreateRequest ...), editClubApplication(..., ClubApplicationEditRequest ...).
- backend/src/main/java/moadong/club/controller/ClubApplyController.java: @PostMapping("/application"), @PutMapping("/application"), @GetMapping("/apply"), @PostMapping("/apply").
- 조치: 리버트 목적이면 관련 DTO/컨트롤러/서비스를 Form 네이밍·경로로 완전 복원하거나, 리버트가 아니면 PR 제목/설명에서 'revert' 표기를 제거·수정하라.
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/controller/ClubApplyController.java around
lines 12-13, the imported DTO names (ClubApplicationCreateRequest,
ClubApplicationEditRequest) and the controller endpoint paths (/application,
/apply) conflict with the repo's intended "revert" to *Form* naming; either
fully restore the original *Form* naming across DTOs, controller mappings, and
service method signatures or remove/clarify the "revert" in the PR. To fix:
rename DTO classes and their usages to the original Form names (e.g.,
ClubApplicationCreateForm, ClubApplicationEditForm), update the controller
imports and @RequestMapping/@PostMapping/@PutMapping/@GetMapping paths to the
Form-based paths, and adjust ClubApplyService method signatures to accept the
Form types; alternatively, if you did not intend to restore Form names, update
the PR title/description to remove "revert" and keep the current DTO/mapping
names consistent across controller/service.
| import moadong.club.enums.ApplicationStatus; | ||
| import org.springframework.data.mongodb.core.mapping.Document; | ||
|
|
||
| @Document("club_applicants") | ||
| @Document("club_applications") | ||
| @AllArgsConstructor | ||
| @Getter | ||
| @Builder(toBuilder = true) | ||
| public class ClubApplicant { | ||
| public class ClubApplication { | ||
|
|
||
| @Id | ||
| private String id; | ||
|
|
||
| private String formId; | ||
| private String questionId; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Builder.Default | ||
| ApplicantStatus status = ApplicantStatus.SUBMITTED; | ||
| ApplicationStatus status = ApplicationStatus.SUBMITTED; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
Mongo @document에서 jakarta.persistence.Id 사용 — 식별자 매핑 오류 가능성(교체 필요)
@Id는org.springframework.data.annotation.Id를 사용해야 합니다. 현재jakarta.persistence.Id로 되어 있어 식별자 매핑이 비명시/불안정해질 수 있습니다.@Enumerated(EnumType.STRING)역시 JPA 전용으로 Mongo 환경에서는 제거 권장.status필드 접근 제어자가 누락되어 package-private입니다. 일관성을 위해private지정 권장.
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.Id;
+import org.springframework.data.annotation.Id;
@@
- @Enumerated(EnumType.STRING)
- @Builder.Default
- ApplicationStatus status = ApplicationStatus.SUBMITTED;
+ @Builder.Default
+ private ApplicationStatus status = ApplicationStatus.SUBMITTED;레포 전역 점검 스크립트:
Also applies to: 45-47
🏁 Script executed:
#!/bin/bash
# @Document 클래스에서 잘못된 @Id 사용 여부 점검
while IFS= read -r f; do
echo "==> $f"
rg -nP 'import\s+jakarta\.persistence\.Id' "$f" || true
rg -nP 'import\s+org\.springframework\.data\.annotation\.Id' "$f" || true
done < <(rg -nPl --type=java '@Document\(')Length of output: 56
🏁 Script executed:
#!/bin/bash
set -eu
files=$(rg -nPl --type=java '@Document\(' || true)
if [ -z "$files" ]; then
echo "NO_DOCUMENT_FILES"
exit 0
fi
while IFS= read -r f; do
echo "==> $f"
rg -nP 'import\s+jakarta\.persistence\.(Id|Enumerated|EnumType)\b' "$f" || true
rg -nP 'import\s+org\.springframework\.data\.annotation\.Id\b' "$f" || true
rg -nP '@Enumerated\b' "$f" || true
rg -nP '\bApplicationStatus\b' "$f" || true
rg -nP '\bstatus\b' "$f" || true
done <<< "$files"Length of output: 888
Mongo @document 엔티티: jakarta.persistence.Id → org.springframework.data.annotation.Id 교체, @Enumerated 제거, status 접근자 고정 (치명적)
검증 결과: backend/src/main/java/moadong/club/entity/ClubApplication.java에 jakarta.persistence.{Id,Enumerated,EnumType}와 @Enumerated 사용 및 package-private인 status 필드가 확인되었고, backend/src/main/java/moadong/user/entity/User.java와 backend/src/main/java/moadong/club/entity/ClubQuestion.java에서도 jakarta.persistence.Id 사용이 확인되었습니다. backend/src/main/java/moadong/club/entity/Club.java는 이미 올바르게 org.springframework.data.annotation.Id 사용 중입니다.
조치 (간단·직접 수정):
- backend/src/main/java/moadong/club/entity/ClubApplication.java
- imports: remove jakarta.persistence.EnumType, jakarta.persistence.Enumerated, jakarta.persistence.Id → add import org.springframework.data.annotation.Id
- 애노테이션: 제거 @Enumerated(EnumType.STRING)
- 필드: make status private and keep @Builder.Default
- (원래 제안한 diff 적용 가능)
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.Id;
+import org.springframework.data.annotation.Id;
@@
- @Enumerated(EnumType.STRING)
- @Builder.Default
- ApplicationStatus status = ApplicationStatus.SUBMITTED;
+ @Builder.Default
+ private ApplicationStatus status = ApplicationStatus.SUBMITTED;- backend/src/main/java/moadong/user/entity/User.java (line ~3): replace
- import jakarta.persistence.Id; → import org.springframework.data.annotation.Id;
- backend/src/main/java/moadong/club/entity/ClubQuestion.java (line ~3): replace
- import jakarta.persistence.Id; → import org.springframework.data.annotation.Id;
우선순위: 즉시 수정 필요 — Mongo 매핑 실패/식별자 누락 위험 있음.
📝 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.
| import moadong.club.enums.ApplicationStatus; | |
| import org.springframework.data.mongodb.core.mapping.Document; | |
| @Document("club_applicants") | |
| @Document("club_applications") | |
| @AllArgsConstructor | |
| @Getter | |
| @Builder(toBuilder = true) | |
| public class ClubApplicant { | |
| public class ClubApplication { | |
| @Id | |
| private String id; | |
| private String formId; | |
| private String questionId; | |
| @Enumerated(EnumType.STRING) | |
| @Builder.Default | |
| ApplicantStatus status = ApplicantStatus.SUBMITTED; | |
| ApplicationStatus status = ApplicationStatus.SUBMITTED; | |
| import moadong.club.enums.ApplicationStatus; | |
| import org.springframework.data.mongodb.core.mapping.Document; | |
| import org.springframework.data.annotation.Id; | |
| @Document("club_applications") | |
| @AllArgsConstructor | |
| @Getter | |
| @Builder(toBuilder = true) | |
| public class ClubApplication { | |
| @Id | |
| private String id; | |
| private String questionId; | |
| @Builder.Default | |
| private ApplicationStatus status = ApplicationStatus.SUBMITTED; |
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/entity/ClubApplication.java (lines 14-31)
replace jakarta.persistence.Id/Enumerated/EnumType usages with Spring Data
annotations: remove imports of jakarta.persistence.Id,
jakarta.persistence.Enumerated, jakarta.persistence.EnumType and add import
org.springframework.data.annotation.Id; remove the @Enumerated(EnumType.STRING)
annotation from the status field, make the status field private (keep
@Builder.Default and default value ApplicationStatus.SUBMITTED), and ensure
lombok annotations remain; additionally, in
backend/src/main/java/moadong/user/entity/User.java (around line 3) and
backend/src/main/java/moadong/club/entity/ClubQuestion.java (around line 3)
replace import jakarta.persistence.Id with import
org.springframework.data.annotation.Id to use Spring Data MongoDB id mapping.
| @Document("club_questions") | ||
| @AllArgsConstructor | ||
| @Getter | ||
| @Builder(toBuilder = true) | ||
| public class ClubApplicationForm implements Persistable<String> { | ||
| public class ClubQuestion implements Persistable<String> { |
There was a problem hiding this comment.
Mongo @document에서 jakarta.persistence.Id 사용 — 동일 이슈 재발
본 클래스도 jakarta.persistence.Id를 사용 중입니다. org.springframework.data.annotation.Id로 교체해 주세요.
-import jakarta.persistence.Id;
+import org.springframework.data.annotation.Id;📝 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.
| @Document("club_questions") | |
| @AllArgsConstructor | |
| @Getter | |
| @Builder(toBuilder = true) | |
| public class ClubApplicationForm implements Persistable<String> { | |
| public class ClubQuestion implements Persistable<String> { | |
| import org.springframework.data.annotation.Id; | |
| @Document("club_questions") | |
| @AllArgsConstructor | |
| @Getter | |
| @Builder(toBuilder = true) | |
| public class ClubQuestion implements Persistable<String> { |
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/entity/ClubQuestion.java around lines 17
to 21, the class currently uses jakarta.persistence.Id; replace that with
org.springframework.data.annotation.Id by changing the import to
org.springframework.data.annotation.Id, annotate the id field with the Spring
Data Id, remove the jakarta.persistence.Id import, and update any references
(e.g., getters or overrides for getId()) if needed so the Spring Data annotation
is used consistently.
| public interface ClubApplicationRepository extends MongoRepository<ClubApplication, String> { | ||
| @Query("{ 'questionId': ?0, 'status': { $exists: true, $ne: 'DRAFT' } }") | ||
| List<ClubApplication> findAllByQuestionId(String questionId); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
🧩 Analysis chain
조회 성능을 위한 인덱스 권장(questionId, status)
findAllByQuestionId는 status 필터와 함께 사용됩니다. ClubApplication(questionId, status) 복합 인덱스를 추가하세요.
예시(엔티티):
// backend/src/main/java/moadong/club/entity/ClubApplication.java
@CompoundIndex(name = "idx_app_question_status", def = "{'questionId': 1, 'status': 1}")
class ClubApplication { ... }인덱스 선언 존재 여부 확인:
🏁 Script executed:
#!/bin/bash
rg -n --type=java '@CompoundIndex|@Indexed' backend/src/main/java/moadong/club/entity/ClubApplication.javaLength of output: 0
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== PWD ==="
pwd
echo "=== backend src tree: moadong/club ==="
ls -la backend/src/main/java/moadong/club || true
echo "=== backend src tree: moadong/club/entity ==="
ls -la backend/src/main/java/moadong/club/entity || true
echo "=== find ClubApplication.java ==="
if command -v fd >/dev/null 2>&1; then
fd -HI --type f ClubApplication.java || true
else
find . -type f -name 'ClubApplication.java' -print || true
fi
echo "=== rg: class ClubApplication ==="
rg -n --type=java -S 'class\s+ClubApplication\b' || true
echo "=== rg: @CompoundIndex|@CompoundIndexes|@Indexed ==="
rg -n --type=java -S '@CompoundIndex|@CompoundIndexes|@Indexed' || true
echo "=== rg: questionId|clubId fields ==="
rg -n --type=java -S '\b(questionId|clubId)\b' || true
echo "=== rg: Mongo index creation / MongoTemplate / IndexOperations ==="
rg -n --type=java -S 'createIndex|ensureIndex|IndexOperations|MongoTemplate' || true
echo "=== show ClubApplicationRepository.java (if exists) ==="
sed -n '1,240p' backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java || trueLength of output: 17576
ClubApplication 엔티티에 questionId, status 복합 인덱스 추가 필요
backend/src/main/java/moadong/club/entity/ClubApplication.java 클래스 선언부에
@CompoundIndex(name = "idx_app_question_status", def = "{'questionId': 1, 'status': 1}")를 추가하세요.
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java
around lines 9 to 12, the query filters by questionId and status but the
ClubApplication entity lacks a composite index for these fields; open
backend/src/main/java/moadong/club/entity/ClubApplication.java and add the
CompoundIndex annotation to the class declaration with name
"idx_app_question_status" and def "{'questionId': 1, 'status': 1}", ensure the
org.springframework.data.mongodb.core.index.CompoundIndex import is present, and
recompile to make the new index available to MongoDB.
| @Repository | ||
| public interface ClubQuestionRepository extends MongoRepository<ClubQuestion, String> { | ||
|
|
||
| Optional<ClubQuestion> findByClubId(String clubId); | ||
|
|
||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
clubId 조회 경로에 인덱스 필요
findByClubId는 고빈도 조회일 가능성이 높습니다. ClubQuestion.clubId에 인덱스를 추가하세요(1클럽 1문서 정책이면 unique 권장).
예시(엔티티 측):
// backend/src/main/java/moadong/club/entity/ClubQuestion.java
@Indexed(name = "idx_club_question_clubId", unique = true)
private String clubId;인덱스 유무 확인 스크립트:
🏁 Script executed:
#!/bin/bash
rg -n --type=java '@Indexed' | rg -n 'ClubQuestion|clubId' -nLength of output: 0
🏁 Script executed:
#!/usr/bin/env bash
# ClubQuestion 엔티티 파일 경로 조회
entity_file=$(rg -l --type=java 'class ClubQuestion' -g "*.java")
echo "ClubQuestion 엔티티 파일 경로: $entity_file"
# @Indexed 어노테이션 존재 여부 확인
echo "- @Indexed 어노테이션 검색:"
rg -n '@Indexed' -C2 "$entity_file" || echo " > @Indexed 어노테이션 없음"
# clubId 필드 정의 확인
echo "- clubId 필드 검색:"
rg -n 'private\s\+String\s\+clubId' -C2 "$entity_file" || echo " > clubId 필드 없음"Length of output: 947
추가 확인을 위해 다음 스크립트를 실행합니다.
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
target="backend/src/main/java/moadong/club/entity/ClubQuestion.java"
repo_file="backend/src/main/java/moadong/club/repository/ClubQuestionRepository.java"
echo "== ClubQuestion 엔티티 파일 확인 =="
if [ -f "$target" ]; then
echo "파일: $target"
sed -n '1,400p' "$target"
else
echo "파일 없음: $target"
fi
echo
echo "== @Indexed 어노테이션 전체 검색 (java 파일) =="
rg -n --type=java '@Indexed' -S || echo "none"
echo
echo "== clubId / questionId 전역 검색 =="
rg -n --hidden -S '\bclubId\b' || echo "clubId: none"
rg -n --hidden -S '\bquestionId\b' || echo "questionId: none"
echo
echo "== ClubQuestionRepository 파일 내용 =="
if [ -f "$repo_file" ]; then
sed -n '1,200p' "$repo_file"
else
echo "파일 없음: $repo_file"
fiLength of output: 26967
clubId 조회 경로에 인덱스 추가 필요
- backend/src/main/java/moadong/club/entity/ClubQuestion.java의 clubId 필드에 인덱스 추가하세요. 예: import org.springframework.data.mongodb.core.index.Indexed; @indexed(name = "idx_club_question_clubId", unique = true) private String clubId;
- 1클럽-1문서 정책이면 unique 권장; unique 적용 전 기존 데이터 중복 확인 및 필요시 마이그레이션 진행하세요.
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/entity/ClubQuestion.java around the clubId
field (add index for repository lookup), annotate the clubId field with Spring
Data MongoDB's @Indexed and import
org.springframework.data.mongodb.core.index.Indexed; e.g. add @Indexed(name =
"idx_club_question_clubId", unique = true) to the field if your model enforces
one-club-one-document; if you choose unique=true, first scan existing documents
for duplicate clubId values and run a migration to deduplicate or correct them
before deploying the index change.
| @Transactional | ||
| public void editClubApplication(String clubId, CustomUserDetails user, ClubApplicationEditRequest request) { | ||
| ClubQuestion clubQuestion = getClubQuestion(clubId, user); | ||
|
|
||
| public void createClubApplicationForm(String clubId, CustomUserDetails user, ClubApplicationFormCreateRequest request) { | ||
| validateClubOwner(clubId, user); | ||
| validateSemester(request.semesterYear(), request.semesterTerm()); | ||
|
|
||
| ClubApplicationForm clubApplicationForm = createQuestions( | ||
| ClubApplicationForm.builder() | ||
| .clubId(clubId) | ||
| .semesterYear(request.semesterYear()) | ||
| .semesterTerm(request.semesterTerm()) | ||
| .build(), | ||
| request); | ||
| clubApplicationFormsRepository.save(createQuestions(clubApplicationForm, request)); | ||
| clubQuestion.updateEditedAt(); | ||
| clubQuestionRepository.save(updateQuestions(clubQuestion, request)); | ||
| } | ||
|
|
There was a problem hiding this comment.
edit에서 문서 자동 생성은 혼동 유발 — 존재하지 않으면 404로 처리 권장
현재 getClubQuestion이 미존재 시 새 문서를 만들어 edit 경로에서도 생성이 일어납니다. edit는 명시적으로 존재해야 하므로 없으면 예외를 던지도록 가드 추가를 권장합니다.
@Transactional
public void editClubApplication(String clubId, CustomUserDetails user, ClubApplicationEditRequest request) {
- ClubQuestion clubQuestion = getClubQuestion(clubId, user);
+ ClubQuestion clubQuestion = getClubQuestion(clubId, user);
+ if (clubQuestion.getId() == null) {
+ throw new RestApiException(ErrorCode.APPLICATION_NOT_FOUND);
+ }
clubQuestion.updateEditedAt();
clubQuestionRepository.save(updateQuestions(clubQuestion, request));
}🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/service/ClubApplyService.java around lines
56 to 63, the edit flow currently calls getClubQuestion which auto-creates a
missing document; change this so edit fails fast with a 404: replace the call to
getClubQuestion with a repository lookup (e.g.,
clubQuestionRepository.findByClubIdAndUser(...) or equivalent) and if the entity
is absent throw a NotFoundException (or your existing 404-mapped exception);
then proceed to update the entity, set editedAt and save the updated entity — do
not create a new ClubQuestion in the edit method.
| @Transactional | ||
| public void editClubApplication(String clubId, CustomUserDetails user, ClubApplicationEditRequest request) { |
There was a problem hiding this comment.
Mongo 트랜잭션 어노테이션 교체 제안
jakarta.transaction.Transactional 보다는 Spring의 org.springframework.transaction.annotation.Transactional 사용을 권장합니다(트랜잭션 매니저 연동).
추가(파일 상단 import 교체):
-import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;Also applies to: 167-168
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/service/ClubApplyService.java around lines
56-57 (and also lines 167-168), replace the use of
jakarta.transaction.Transactional with Spring's
org.springframework.transaction.annotation.Transactional: update the import at
the top of the file to import
org.springframework.transaction.annotation.Transactional, remove the
jakarta.transaction import, and keep the existing @Transactional annotations on
the methods so they use Spring's transaction manager integration.
| // 테스트 전에 문서를 한 번만 조회하여 ID를 확보 | ||
| if (clubApplicationFormsRepository.findByClubId(club.getId()).isEmpty()){ | ||
| throw new NoSuchElementException("테스트를 위한 ClubApplicationForm 문서가 DB에 존재하지 않습니다. 먼저 문서를 생성해주세요."); | ||
| } | ||
| ClubApplicationForm clubApplicationForm = clubApplicationFormsRepository.findByClubId(club.getId()).get(0); | ||
| this.clubApplicationFormId = clubApplicationForm.getId(); | ||
| ClubQuestion clubQuestion = clubQuestionRepository.findByClubId(club.getId()) | ||
| .orElseThrow(() -> new NoSuchElementException("테스트를 위한 ClubQuestion 문서가 DB에 존재하지 않습니다. 먼저 문서를 생성해주세요.")); | ||
| this.clubQuestionId = clubQuestion.getId(); | ||
| } |
There was a problem hiding this comment.
테스트가 외부 DB 상태에 의존
사전 문서 미존재 시 테스트가 실패합니다. setUp에서 문서를 없으면 생성하도록 self‑contained하게 만들어 주세요.
예시:
var clubQuestion = clubQuestionRepository.findByClubId(club.getId())
.orElseGet(() -> clubQuestionRepository.save(ClubQuestion.builder().clubId(club.getId()).build()));
this.clubQuestionId = clubQuestion.getId();🤖 Prompt for AI Agents
In backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java around
lines 49-53, the setup currently throws if the ClubQuestion is missing causing
tests to depend on external DB state; change the lookup to create-and-save a
ClubQuestion when absent (use findByClubId(...).orElseGet(() ->
clubQuestionRepository.save(new ClubQuestion or builder with clubId set to
club.getId()))), then assign this.clubQuestionId from the returned entity so the
test is self-contained.
|
|
||
| import moadong.club.payload.request.ClubApplicationFormEditRequest; | ||
| import java.util.ArrayList; | ||
| import moadong.club.payload.request.ClubApplicationEditRequest; |
There was a problem hiding this comment.
🧩 Analysis chain
빈 질문 목록 허용 여부 확인
Fixture가 빈 questions 리스트를 사용합니다. 서버 검증/프론트 기대치가 “최소 1문항”이라면 테스트 데이터에 최소 문항을 포함시키세요(또는 @SiZe(min=1)로 명세).
Also applies to: 7-7, 17-21
🏁 Script executed:
#!/bin/bash
# 질문 개수 제한 검증 규칙 존재 여부 탐색
rg -nP --type=java '@Size\s*\(\s*min\s*=\s*1' backend/src/main/java/moadongLength of output: 0
🏁 Script executed:
#!/bin/bash
# ClubApplicationEditRequest DTO 파일 위치 찾기
dto=$(find backend/src/main/java -name ClubApplicationEditRequest.java)
echo "DTO 파일 경로: $dto"
# questions 필드 및 validation 어노테이션 확인
rg -n 'questions' -C3 "$dto"Length of output: 192
DTO에 questions 최소 1개 제약 추가하거나 테스트 픽스처에 질문 포함
검증 결과: backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java의 questions 필드에 @NotNull, @Valid만 있고 @SiZe(min=1)이 없습니다 — 서버는 빈 리스트를 허용합니다. 프론트가 최소 1문항을 기대하면 다음 중 하나를 적용하세요:
- DTO에 @SiZe(min=1) 추가 — backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java (18-20행).
- 또는 테스트 픽스처에서 빈 questions 대신 최소 1문항 포함.
🤖 Prompt for AI Agents
In backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java around
line 4, the test fixture imports ClubApplicationEditRequest but uses an empty
questions list while the DTO lacks a @Size(min=1) constraint; fix by either
updating the DTO
backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java
(around lines 18-20) to add @Size(min=1) on the questions field alongside
@NotNull and @Valid, or modify this test fixture to populate the questions list
with at least one valid question entry so tests match front-end expectations.
Reverts #739
운영 프론트와의 호환성 이슈로 PR #739의 변경을 되돌립니다.
Summary by CodeRabbit