Skip to content

[Release] BE v1.0.5 배포#626

Merged
lepitaaar merged 9 commits intomainfrom
develop/be
Aug 9, 2025
Merged

[Release] BE v1.0.5 배포#626
lepitaaar merged 9 commits intomainfrom
develop/be

Conversation

@lepitaaar
Copy link
Contributor

@lepitaaar lepitaaar commented Aug 9, 2025

#️⃣연관된 이슈

📝작업 내용

지원서 메모 API
지원서 상태 변경 API
지원서 삭제 API 추가

지원자의 지원서를 응답해주는 API의 응답 명세가 변경되어 FE 배포보다 우선시되어야합니다.

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • 신규 기능

    • 동아리 지원자 개별 정보(메모, 상태)를 수정 및 삭제할 수 있는 API가 추가되었습니다.
    • 지원자 정보에 메모 필드가 추가되었습니다.
    • 지원자 목록 조회 시 생성일(createdAt) 정보가 제공됩니다.
  • 버그 수정

    • 지원자 정보가 존재하지 않을 때의 에러 메시지가 추가되었습니다.
  • 기타

    • 지원자 수정 요청 시 메모 길이(최대 500자)와 필수 항목 검증이 적용됩니다.

@lepitaaar lepitaaar self-assigned this Aug 9, 2025
@lepitaaar lepitaaar added 💾 BE Backend 📈 release 릴리즈 배포 labels Aug 9, 2025
@vercel
Copy link

vercel bot commented Aug 9, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
moadong ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 9, 2025 0:25am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 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

개별 동아리 지원자 정보 관리 기능이 추가되었습니다. ClubApplyController에 지원자 정보 수정(메모, 상태) 및 삭제를 위한 PUT, DELETE 엔드포인트가 생성되었고, 이를 처리하는 서비스 로직, 요청 DTO, 엔티티 필드 및 예외 코드가 추가·수정되었습니다. DTO, 엔티티, 레포지토리, 예외 코드 등 관련 클래스가 함께 변경되었습니다.

Changes

Cohort / File(s) Change Summary
Controller 및 엔드포인트 추가
backend/src/main/java/moadong/club/controller/ClubApplyController.java
PUT /api/club/{clubId}/apply/{appId}로 지원자 정보(메모, 상태) 수정, DELETE /api/club/{clubId}/apply/{appId}로 지원자 삭제 엔드포인트 추가. 인증 필요.
엔티티 필드 및 메서드 확장
backend/src/main/java/moadong/club/entity/ClubApplication.java
memo 필드(기본값 ""), 메모 및 상태 동시 갱신용 updateDetail 메서드 추가.
지원자 결과 DTO 확장
backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java
필드명 questionIdid로 변경, createdAt 필드 추가, factory 메서드 수정.
지원자 수정 요청 DTO 추가
backend/src/main/java/moadong/club/payload/request/ClubApplicantEditRequest.java
memo(최대 500자, not null), status(not null) 필드를 가진 record 타입 요청 DTO 신설.
레포지토리 메서드 추가
backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java
findByIdAndQuestionId(Optional 반환) 메서드 추가.
서비스 로직 추가
backend/src/main/java/moadong/club/service/ClubApplyService.java
지원자 정보 수정(editApplicantDetail), 삭제(deleteApplicant) 서비스 메서드 추가. 권한 및 예외 처리 포함.
예외 코드 추가
backend/src/main/java/moadong/global/exception/ErrorCode.java
APPLICANT_NOT_FOUND(900-2, 404) 예외 코드 추가, enum trailing comma 보정.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Repository
    participant Entity

    Client->>Controller: PUT /api/club/{clubId}/apply/{appId} (memo, status)
    Controller->>Service: editApplicantDetail(clubId, appId, request, user)
    Service->>Repository: find Club by clubId
    Service->>Repository: find ClubApplication by appId & clubId
    Service->>Entity: updateDetail(memo, status)
    Service->>Repository: save(ClubApplication)
    Service-->>Controller: 완료
    Controller-->>Client: 성공 응답

    Client->>Controller: DELETE /api/club/{clubId}/apply/{appId}
    Controller->>Service: deleteApplicant(clubId, appId, user)
    Service->>Repository: find Club by clubId
    Service->>Repository: find ClubApplication by appId & clubId
    Service->>Repository: delete(ClubApplication)
    Service-->>Controller: 완료
    Controller-->>Client: 성공 응답
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Moadong/moadong#621: 지원자 상세 수정 엔드포인트 관련 기능 확장 및 중복 코드 영역 있음.
  • Moadong/moadong#620: 메모 편집 기능 추가와 중복되는 로직이 있으나, 본 PR은 삭제 및 상태 변경 등 더 확장된 기능 포함.
  • Moadong/moadong#622: 본 PR과 거의 동일한 엔드포인트, 서비스, DTO, 예외 코드 추가 등 코드 변경 내용이 직접적으로 일치.

Suggested reviewers

  • Zepelown
  • seongwon030
  • PororoAndFriends

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop/be

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 3

🧹 Nitpick comments (8)
backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java (1)

14-14: 중복·일관성 검토 필요

id 자체가 고유키이므로 questionId까지 추가로 조건에 넣는 것이 과도할 수 있습니다. 반대로 findAllByQuestionId에서는 status != DRAFT 조건을 명시했는데, 여기에는 빠져 있어 동일한 비즈니스 규칙이 깨집니다.
· questionId-검증이 꼭 필요하다면 서비스 레이어에서 처리하고, 레포지토리는 findById만 제공해 단일 책임을 유지하는 방안을 고려해 주세요.
· 또는 쿼리 메서드에 status 필터를 맞춰 일관성을 유지해 주세요.

backend/src/main/java/moadong/club/entity/ClubApplication.java (2)

33-35: 엔티티 레벨 길이 제약 추가 권장

memo 필드는 요청 DTO에서 @Size(max = 500)로 제한하지만, 엔티티에는 제약이 없습니다.
DB에 500자를 초과한 값이 저장될 가능성을 차단하려면 @Size 또는 스키마 제약을 엔티티에도 선언해 주세요.


42-45: null 방어 로직 고려

updateDetail에서 memo, status가 그대로 할당됩니다. 서비스 단에서 validation이 있더라도 방어 코드(예: Objects.requireNonNull(status))를 두면 예기치 못한 NPE를 예방할 수 있습니다.

backend/src/main/java/moadong/club/payload/request/ClubApplicantEditRequest.java (1)

7-14: 빈 문자열 허용 여부 확인 필요

memo@NotNull만 적용되어 공백 문자열은 허용됩니다. 빈 메모도 허용 의도라면 문제없지만, 최소 한 글자 이상을 요구하려면 @NotBlank로 변경해 주세요.

backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java (1)

19-23: 응답 DTO가 엔티티를 직접 노출

answers 타입으로 ClubQuestionAnswer 엔티티를 그대로 반환하면 계층 간 결합이 강화됩니다. 가벼운 응답용 DTO로 매핑해 노출 범위를 최소화하는 것을 권장합니다.

backend/src/main/java/moadong/club/service/ClubApplyService.java (1)

3-3: 트랜잭션 어노테이션 일관성: Spring @transactional로 통일 권장

현재 jakarta.transaction.Transactional을 사용하고 있습니다. 프로젝트 전반의 일관성과 세밀한 설정(예: propagation, isolation, timeout, readOnly 등)을 위해 org.springframework.transaction.annotation.Transactional 사용을 권장합니다.

-import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;
backend/src/main/java/moadong/club/controller/ClubApplyController.java (2)

73-86: PUT 지원자 수정: 문구 오타(‘자원자’→‘지원자’), @Valid 사용 권장, PATCH/204 고려

  • Swagger 문구에서 ‘자원자’ → ‘지원자’로 정정 제안.
  • DTO 검증은 @Validated 대신 @Valid 사용을 권장합니다(중첩 필드 검증 일관성).
  • 부분 수정 성격이면 PUT 대신 PATCH와 응답 204 No Content도 고려해 주세요.

오타/검증 애노테이션 최소 변경 diff:

-    @Operation(summary = "지원서 변경",
-            description = "클럽 자원자의 지원서 정보를 수정합니다.<br>"
+    @Operation(summary = "지원서 변경",
+            description = "클럽 지원자의 지원서 정보를 수정합니다.<br>"
                     + "appId - 지원서 아이디"
     )
@@
-                                                 @RequestBody @Validated ClubApplicantEditRequest request,
+                                                 @RequestBody @Valid ClubApplicantEditRequest request,

필요 시 상단 임포트 추가:

import jakarta.validation.Valid;

88-101: DELETE 지원자 삭제: 문구 오타 정정, 메서드명 일관성, 204 No Content 권장

  • Swagger 문구의 ‘자원자’ → ‘지원자’ 정정 제안.
  • 서비스 메서드가 deleteApplicant인 만큼 컨트롤러도 메서드명 일관성 유지 제안(removeApplicantdeleteApplicant).
  • 삭제 성공 시 바디 없이 204 No Content 응답을 고려해 주세요.

최소 변경 diff:

-    @Operation(summary = "지원서 삭제",
-            description = "클럽 자원자의 지원서를 삭제합니다.<br>"
+    @Operation(summary = "지원서 삭제",
+            description = "클럽 지원자의 지원서를 삭제합니다.<br>"
                     + "appId - 지원서 아이디"
     )
@@
-    public ResponseEntity<?> removeApplicant(@PathVariable String clubId,
+    public ResponseEntity<?> deleteApplicant(@PathVariable String clubId,
                                              @PathVariable String appId,
                                              @CurrentUser CustomUserDetails user) {
         clubApplyService.deleteApplicant(clubId, appId, user);
-        return Response.ok("success delete applicant");
+        return Response.ok("success delete applicant"); // 팀 컨벤션에 따라 204 No Content 고려
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52c1d26 and f04def2.

📒 Files selected for processing (7)
  • backend/src/main/java/moadong/club/controller/ClubApplyController.java (2 hunks)
  • backend/src/main/java/moadong/club/entity/ClubApplication.java (1 hunks)
  • backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java (2 hunks)
  • backend/src/main/java/moadong/club/payload/request/ClubApplicantEditRequest.java (1 hunks)
  • backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java (1 hunks)
  • backend/src/main/java/moadong/club/service/ClubApplyService.java (2 hunks)
  • backend/src/main/java/moadong/global/exception/ErrorCode.java (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
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.
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: Zepelown
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:32-33
Timestamp: 2025-05-15T12:03:57.356Z
Learning: 엔티티 클래스는 요청/응답 객체(DTO)에 의존해서는 안 됩니다. 계층 간 의존성 문제를 방지하기 위해 엔티티와 DTO는 분리되어야 합니다. 예를 들어, `ClubApplicationQuestion` 엔티티가 `ClubApplicationRequest.Options`와 같은 요청 객체를 직접 참조하는 대신, 엔티티 패키지 내에 `QuestionOptions`와 같은 별도의 클래스를 정의하고 사용해야 합니다. 이렇게 하면 요청 객체 변경이 데이터베이스 스키마나 엔티티 계층에 영향을 미치지 않습니다.
📚 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/entity/ClubApplication.java
  • backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicantEditRequest.java
  • backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java
  • backend/src/main/java/moadong/club/service/ClubApplyService.java
  • backend/src/main/java/moadong/club/controller/ClubApplyController.java
📚 Learning: 2025-05-15T12:03:57.356Z
Learnt from: Zepelown
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/entity/ClubApplicationQuestion.java:32-33
Timestamp: 2025-05-15T12:03:57.356Z
Learning: 엔티티 클래스는 요청/응답 객체(DTO)에 의존해서는 안 됩니다. 계층 간 의존성 문제를 방지하기 위해 엔티티와 DTO는 분리되어야 합니다. 예를 들어, `ClubApplicationQuestion` 엔티티가 `ClubApplicationRequest.Options`와 같은 요청 객체를 직접 참조하는 대신, 엔티티 패키지 내에 `QuestionOptions`와 같은 별도의 클래스를 정의하고 사용해야 합니다. 이렇게 하면 요청 객체 변경이 데이터베이스 스키마나 엔티티 계층에 영향을 미치지 않습니다.

Applied to files:

  • backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java
  • backend/src/main/java/moadong/club/service/ClubApplyService.java
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.

Applied to files:

  • backend/src/main/java/moadong/club/service/ClubApplyService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy
🔇 Additional comments (3)
backend/src/main/java/moadong/club/payload/dto/ClubApplicantsResult.java (1)

40-44: 생성일 추가 👍, 하지만 정렬 기준 확인

createdAt 필드가 추가되었으므로, 목록 API에서 기본 정렬(예: 최신순)이 필요한지 서비스/레포지토리 레이어 로직을 함께 점검해 주세요.

backend/src/main/java/moadong/club/service/ClubApplyService.java (1)

9-9: 요청 DTO 임포트 추가 LGTM

ClubApplicantEditRequest 추가 적절합니다.

backend/src/main/java/moadong/club/controller/ClubApplyController.java (1)

7-7: 요청 DTO 임포트 추가 LGTM

ClubApplicantEditRequest 임포트 추가 적절합니다.

Comment on lines +128 to +143
@Transactional
public void editApplicantDetail(String clubId, String appId, ClubApplicantEditRequest request, CustomUserDetails user) {
Club club = clubRepository.findById(clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));

if (!user.getId().equals(club.getUserId())) {
throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
}

ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));

application.updateDetail(request.memo(), request.status());

clubApplicationRepository.save(application);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

지원자 수정: 상태 전이 검증·동시성 보호(@Version)·중복 로직 추출 제안

  • 권한 검증/조회 흐름은 적절합니다.
  • 상태 전이 검증: 도메인 규칙(예: SCREENING → SCREENING_PASSED → …) 유효성 체크가 필요합니다. 서비스 레벨에서 허용되지 않는 전이를 차단하세요.
  • 동시성: 다중 관리자가 동시에 수정 시 마지막 저장이 덮어쓸 수 있습니다. ClubApplication 엔티티에 @Version(낙관적 락)을 도입하는 것을 권장합니다.
  • 중복 제거: 클럽 조회+권한검사, 지원서 조회 로직이 deleteApplicant와 반복됩니다. 헬퍼 메서드로 추출하면 가독성과 재사용성이 좋아집니다.
  • 저장 호출: JPA 영속 상태라면 dirty checking으로 flush 됩니다. 명시적 save 유지/제거는 팀 컨벤션에 맞추세요.

중복 제거용 최소 변경 diff:

-        Club club = clubRepository.findById(clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
-
-        if (!user.getId().equals(club.getUserId())) {
-            throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
-        }
+        Club club = getAuthorizedClubOrThrow(clubId, user);

-        ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));
+        ClubApplication application = getApplicantOrThrow(appId, clubId);

헬퍼 메서드(파일 내 적절한 위치에 추가):

private Club getAuthorizedClubOrThrow(String clubId, CustomUserDetails user) {
    Club club = clubRepository.findById(clubId)
        .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
    if (!user.getId().equals(club.getUserId())) {
        throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
    }
    return club;
}

private ClubApplication getApplicantOrThrow(String appId, String clubId) {
    return clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
        .orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));
}

상태/동시성 관련 확인 스크립트:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) ClubApplication에 @Version(낙관적 락) 존재 여부 확인
fd -t f 'ClubApplication.java' | xargs -I {} rg -n --context 2 '@Version|class ClubApplication' {}

# 2) ClubApplicantEditRequest의 검증 애노테이션 확인(메모/상태 Null 허용 여부)
fd -t f 'ClubApplicantEditRequest.java' | xargs -I {} sed -n '1,200p' {}

# 3) Repository 시그니처 및 의미 확인
fd -t f 'ClubApplicationRepository.java' | xargs -I {} sed -n '1,200p' {}
rg -n 'findByIdAndQuestionId\(' -A 3

Length of output: 2625


지원자 수정 작업에 아래 사항 보강 필요

  • 요청 DTO 검증 확인
    ClubApplicantEditRequestmemo(@NotNull, @SiZe(max=500))와 status(@NotNull)에 Bean Validation이 적용되어 있습니다.
  • 상태 전이 검증
    ClubApplication.updateDetail(...) 내부에 SCREENING → SCREENING_PASSED 등 도메인 전이 유효성을 체크하는 로직이 없어 서비스 레이어에서 불법 전이를 차단해야 합니다.
  • 동시성 보호(@Version)
    ClubApplication 엔티티에 @Version 필드가 없어 다중 관리자 동시 수정 시 데이터 손실 위험이 있습니다.
  • 중복 로직 추출
    editApplicantDetaildeleteApplicant에서 클럽 조회·권한 검증, 지원서 조회 로직이 반복됩니다. 헬퍼 메서드로 분리해 가독성과 재사용성을 높이세요.
  • 저장 호출 일관성
    MongoRepository는 변경 감지를 통해 저장하지만, 명시적 save(...) 호출 여부를 팀 컨벤션에 맞춰 통일하세요.

필요한 최소 변경 예시:

-        Club club = clubRepository.findById(clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
-
-        if (!user.getId().equals(club.getUserId())) {
-            throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
-        }
+        Club club = getAuthorizedClubOrThrow(clubId, user);

-        ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));
+        ClubApplication application = getApplicantOrThrow(appId, clubId);

헬퍼 메서드 예시 (클래스 내 적절한 위치에 추가):

private Club getAuthorizedClubOrThrow(String clubId, CustomUserDetails user) {
    Club club = clubRepository.findById(clubId)
        .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
    if (!user.getId().equals(club.getUserId())) {
        throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
    }
    return club;
}

private ClubApplication getApplicantOrThrow(String appId, String clubId) {
    return clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
        .orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));
}

낙관적 락 적용을 위해 ClubApplication 엔티티에 다음 필드를 추가하세요:

@Version
private Long version;
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/service/ClubApplyService.java around lines
128 to 143, enhance the editApplicantDetail method by first validating the
ClubApplicantEditRequest DTO using Bean Validation annotations already present
on memo and status fields. Add explicit state transition checks in the service
layer to prevent illegal transitions like SCREENING to SCREENING_PASSED before
calling updateDetail. Introduce an @Version field in the ClubApplication entity
to enable optimistic locking and prevent concurrent update conflicts. Extract
repeated logic for club retrieval and authorization as well as applicant
retrieval into private helper methods to improve code reuse and readability.
Finally, ensure consistent use of save calls on the repository according to team
conventions, either always calling save explicitly or relying on change
detection uniformly.

Comment on lines +145 to +158
@Transactional
public void deleteApplicant(String clubId, String appId, CustomUserDetails user) {
Club club = clubRepository.findById(clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));

if (!user.getId().equals(club.getUserId())) {
throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
}

ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));

clubApplicationRepository.delete(application);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

지원자 삭제: 연관 엔티티 삭제 보장, 감사 로깅, 중복 로직 추출 제안

  • 연관 데이터(예: answers)가 있는 경우 FK 제약/고아 제거 설정이 필요합니다. @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL) 등으로 삭제 일관성을 보장해 주세요.
  • 감사/감사추적 요구가 있다면 soft delete(상태 플래그)나 감사 로그 기록 고려를 권장합니다.
  • getAuthorizedClubOrThrow, getApplicantOrThrow로 중복 조회/검증 로직을 통합하면 유지보수성이 향상됩니다.

중복 제거용 최소 변경 diff:

-        Club club = clubRepository.findById(clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
-
-        if (!user.getId().equals(club.getUserId())) {
-            throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
-        }
+        Club club = getAuthorizedClubOrThrow(clubId, user);

-        ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));
+        ClubApplication application = getApplicantOrThrow(appId, clubId);

연관 삭제 설정 점검 스크립트:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# answers 매핑에 orphanRemoval / cascade 설정 확인
fd -t f 'ClubApplication.java' | xargs -I {} rg -n --context 5 'answers|@OneToMany|@ElementCollection|cascade|orphanRemoval' {}

Length of output: 509


중복 로직 추출 및 연관 엔티티 삭제 설정 적용 제안

ClubApplication 삭제 시 답변(answers)이 JPA 매핑 없이 일반 컬렉션으로만 선언되어 있어, 연관 엔티티가 영속성 컨텍스트에서 함께 삭제되지 않습니다. 또한 서비스 레이어의 조회·검증 로직도 중복되고 있어 아래 사항을 적용해 주세요.

• ClubApplication.answers 필드에 JPA 매핑 추가

  • @onetomany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true) 등으로 답변 삭제 일관성 보장
    • ClubApplyService 내 중복 조회·검증 로직 추출
  • getAuthorizedClubOrThrow(clubId, user)
  • getApplicantOrThrow(appId, clubId)
    • 감사 트래킹(soft delete or audit log) 필요 시 별도 구현 고려

예시 최소 변경 diff:

--- a/backend/src/main/java/moadong/club/entity/ClubApplication.java
+++ b/backend/src/main/java/moadong/club/entity/ClubApplication.java
@@
-    @Builder.Default
-    private List<ClubQuestionAnswer> answers = new ArrayList<>();
+    @OneToMany(
+        mappedBy = "application",
+        cascade = CascadeType.ALL,
+        orphanRemoval = true
+    )
+    @Builder.Default
+    private List<ClubQuestionAnswer> answers = new ArrayList<>();
--- a/backend/src/main/java/moadong/club/service/ClubApplyService.java
+++ b/backend/src/main/java/moadong/club/service/ClubApplyService.java
@@ public void deleteApplicant(String clubId, String appId, CustomUserDetails user) {
-        Club club = clubRepository.findById(clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
-
-        if (!user.getId().equals(club.getUserId())) {
-            throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
-        }
+        Club club = getAuthorizedClubOrThrow(clubId, user);
@@
-        ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
-                .orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));
+        ClubApplication application = getApplicantOrThrow(appId, clubId);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/service/ClubApplyService.java around lines
145 to 158, the deleteApplicant method has duplicated club and applicant
retrieval and authorization logic, and the ClubApplication entity's answers
collection lacks JPA cascade settings causing orphaned data. Refactor by
extracting the club retrieval and authorization into a getAuthorizedClubOrThrow
method and the applicant retrieval into getApplicantOrThrow method to remove
duplication. Also, update the ClubApplication.answers field to use
@OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval =
true) to ensure related answers are deleted automatically with the application.
Consider adding audit or soft delete separately if needed.

Comment on lines +42 to 44
AES_CIPHER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "900-1", "암호화 중 오류가 발생했습니다."),
APPLICANT_NOT_FOUND(HttpStatus.NOT_FOUND, "900-2", "지원서가 존재하지 않습니다."),
;
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

오류 코드 중복 정의

APPLICATION_NOT_FOUND(800-1)와 APPLICANT_NOT_FOUND(900-2)가 동일한 메시지와 의미를 갖고 있어 혼동을 유발합니다.
하나의 코드로 통합하거나, 명확히 다른 도메인을 나타내도록 메시지·코드를 재조정해 주세요.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/global/exception/ErrorCode.java around lines 42
to 44, the error codes APPLICATION_NOT_FOUND (800-1) and APPLICANT_NOT_FOUND
(900-2) have overlapping meanings and messages, causing confusion. Review these
two error codes and either merge them into a single unified error code or
differentiate their messages and codes clearly to reflect distinct domains or
error contexts. Update the enum definitions accordingly to avoid duplication and
ambiguity.

@PororoAndFriends PororoAndFriends self-requested a review August 9, 2025 04:54
Copy link
Collaborator

@PororoAndFriends PororoAndFriends left a comment

Choose a reason for hiding this comment

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

고생하셨습니다

@lepitaaar lepitaaar merged commit 86b95b6 into main Aug 9, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💾 BE Backend 📈 release 릴리즈 배포

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments