Skip to content

Comments

Revert "[feature] 동아리 지원서 폼 제작 시에 학기를 선택할 수 있고 생성된 모든 지원서 폼을 학기별로 분류하여 조회할 수 있다"#750

Merged
alsdddk merged 1 commit intodevelop/befrom
revert-739-feature/#732-applicaton-semester-selection-MOA-231
Sep 21, 2025
Merged

Revert "[feature] 동아리 지원서 폼 제작 시에 학기를 선택할 수 있고 생성된 모든 지원서 폼을 학기별로 분류하여 조회할 수 있다"#750
alsdddk merged 1 commit intodevelop/befrom
revert-739-feature/#732-applicaton-semester-selection-MOA-231

Conversation

@alsdddk
Copy link
Collaborator

@alsdddk alsdddk commented Sep 20, 2025

Reverts #739

운영 프론트와의 호환성 이슈로 PR #739의 변경을 되돌립니다.

Summary by CodeRabbit

  • New Features
    • 클럽 지원서 조회·생성·수정 API 정비
    • 지원 현황 조회 제공
    • 지원자 상세 일괄 수정/삭제 지원
  • Refactor
    • 도메인 용어 통일 및 엔드포인트 경로 단순화(applicationFormId 제거)
    • 학기 관련 기능·옵션 전면 제거로 플로우 간소화
    • 상태 명칭을 “지원서 상태”로 일원화
  • Chores
    • “지원서가 존재하지 않습니다.”로 오류 메시지 문구 개선
  • Tests
    • 변경된 도메인/레포지토리에 맞춰 테스트 전면 업데이트

@alsdddk alsdddk self-assigned this Sep 20, 2025
@vercel
Copy link

vercel bot commented Sep 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Sep 20, 2025 2:52pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 20, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

클럽 지원(신청) 도메인을 “Form” 중심에서 “Question/Application” 중심으로 전면 리네이밍 및 구조 변경. 학기(Semester) 관련 타입/엔드포인트/DTO/저장소를 제거. 컨트롤러·서비스·엔티티·리포지토리 전반의 시그니처와 경로를 갱신하고, 배치형 지원자 편집/삭제와 단순화된 조회/생성/수정 플로우로 통합.

Changes

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로 변경. 콜렉션명 변경, formIdquestionId. 학기 필드/메서드 삭제. 관련 메서드 시그니처 갱신.
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

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 ⚠️ Warning 이 PR은 지원서 폼 생성 시 학기 선택 기능을 제거하여 MOA-231 링크 이슈의 요구사항인 학기 선택 기능 추가 요구와 정면으로 상충하며, 요구사항을 충족하지 않습니다. MOA-231에 따라 학기 선택 기능을 복원하거나 별도의 PR로 리버트 작업을 분리해야 합니다.
Out of Scope Changes Check ⚠️ Warning 이 PR은 학기 관련 코드 제거 외에도 엔티티, 컨트롤러, DTO 및 리포지토리 인터페이스 대규모 리네이밍 등 MOA-231 이슈 범위와 무관한 광범위한 리팩토링을 포함하고 있습니다. 학기 선택 코드 리버트만 포함하도록 범위를 제한하거나 리팩토링 및 명명 변경은 별도 PR로 분리하세요.
Docstring Coverage ⚠️ Warning 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.

❤️ Share

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

@github-actions
Copy link

Test Results

72 tests   69 ✅  7s ⏱️
11 suites   3 💤
11 files     0 ❌

Results for commit 7d8573e.

Copy link
Contributor

@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: 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 리스트 전달 시 데이터 소거 가능성

newQuestionsthis.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 검증

  • ClubApplication.updateStatus(ApplicationStatus) 시그니처와 editRequest.status()로 전달되는 enum 값이 도메인 로직(예: SUBMITTED, APPROVED 등)과 정확히 일치하는지 확인
  • memo 필드에 @NotNull만 붙어 빈 문자열이 허용됩니다. 비즈니스 요구가 “빈 값 금지”라면 @notblank 적용을 검토하세요
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.

📥 Commits

Reviewing files that changed from the base of the PR and between b310982 and 7d8573e.

📒 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.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicationEditRequest.java
  • backend/src/main/java/moadong/club/entity/ClubApplication.java
  • backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java
  • backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java
  • backend/src/main/java/moadong/club/service/ClubApplyService.java
  • backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java
  • backend/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.java
  • backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java
  • backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java
  • backend/src/main/java/moadong/club/repository/ClubQuestionRepository.java
  • backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java
  • backend/src/main/java/moadong/club/entity/ClubQuestion.java
  • backend/src/main/java/moadong/club/service/ClubApplyService.java
  • backend/src/test/java/moadong/club/service/ClubApplyServiceTest.java
  • backend/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.

Comment on lines +12 to +13
import moadong.club.payload.request.ClubApplicationCreateRequest;
import moadong.club.payload.request.ClubApplicationEditRequest;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 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)"' || true

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

Comment on lines +14 to 31
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;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Mongo @document에서 jakarta.persistence.Id 사용 — 식별자 매핑 오류 가능성(교체 필요)

  • @Idorg.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.

Suggested change
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.

Comment on lines +17 to +21
@Document("club_questions")
@AllArgsConstructor
@Getter
@Builder(toBuilder = true)
public class ClubApplicationForm implements Persistable<String> {
public class ClubQuestion implements Persistable<String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
@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.

Comment on lines +9 to +12
public interface ClubApplicationRepository extends MongoRepository<ClubApplication, String> {
@Query("{ 'questionId': ?0, 'status': { $exists: true, $ne: 'DRAFT' } }")
List<ClubApplication> findAllByQuestionId(String questionId);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.java

Length 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 || true

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

Comment on lines +8 to +13
@Repository
public interface ClubQuestionRepository extends MongoRepository<ClubQuestion, String> {

Optional<ClubQuestion> findByClubId(String clubId);

}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 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' -n

Length 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"
fi

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

Comment on lines +56 to 63
@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));
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Comment on lines +56 to +57
@Transactional
public void editClubApplication(String clubId, CustomUserDetails user, ClubApplicationEditRequest request) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Comment on lines 49 to 53
// 테스트 전에 문서를 한 번만 조회하여 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();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

테스트가 외부 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;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 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/moadong

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

Copy link
Member

@Zepelown Zepelown left a comment

Choose a reason for hiding this comment

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

확인했습니다

@alsdddk alsdddk merged commit 47f617c into develop/be Sep 21, 2025
5 checks passed
@alsdddk alsdddk deleted the revert-739-feature/#732-applicaton-semester-selection-MOA-231 branch October 12, 2025 12:35
@coderabbitai coderabbitai bot mentioned this pull request Jan 19, 2026
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.

2 participants