Skip to content

[refactor] 지원자를 여러명 삭제할 수 있다.#699

Merged
lepitaaar merged 1 commit intodevelop/befrom
feature/#696-support-multi-delete-application-MOA-196
Aug 25, 2025
Merged

[refactor] 지원자를 여러명 삭제할 수 있다.#699
lepitaaar merged 1 commit intodevelop/befrom
feature/#696-support-multi-delete-application-MOA-196

Conversation

@lepitaaar
Copy link
Contributor

@lepitaaar lepitaaar commented Aug 24, 2025

#️⃣연관된 이슈

#696

📝작업 내용

요청 dto를 배열로 받게 변경되었습니다.

public record ClubApplicantDeleteRequest(
        @NotEmpty
        List<String> applicantIds
) {
}

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

  • New Features
    • 지원자 삭제가 다건 처리로 확장되었습니다. 새 삭제 엔드포인트에서 요청 본문으로 지원자 ID 목록을 전달해 여러 명을 한 번에 삭제할 수 있습니다.
  • Documentation
    • 지원서/지원자 관련 API 요약과 설명 문구를 명확화했습니다.
    • 삭제 API의 경로 및 요청 형식 변경 사항을 반영했습니다.

@lepitaaar lepitaaar self-assigned this Aug 24, 2025
@lepitaaar lepitaaar added 📬 API 서버 API 통신 작업 🔨 Refactor 코드 리팩토링 💾 BE Backend labels Aug 24, 2025
@vercel
Copy link

vercel bot commented Aug 24, 2025

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

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Aug 24, 2025 11:42am

@coderabbitai
Copy link
Contributor

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

삭제 API가 단건(appId 경로 변수)에서 다건(요청 본문 DTO) 삭제로 변경되었습니다. 컨트롤러 엔드포인트와 시그니처가 수정되고, 다건 삭제 요청 DTO가 추가되었습니다. 서비스는 요청된 ID 목록을 조회·검증 후 일괄 삭제하도록 변경되었고, 레포지토리에 목록 조회 메서드가 추가되었습니다.

Changes

Cohort / File(s) Summary
Controller: bulk delete contract
backend/src/main/java/moadong/club/controller/ClubApplyController.java
DELETE 엔드포인트를 /apply/{appId}/applicant로 변경. 메서드 시그니처를 @PathVariable appId@RequestBody ClubApplicantDeleteRequest로 변경. 문서화(summary/description) 문구 업데이트. 서비스 호출을 새 시그니처에 맞게 수정. 임포트 와일드카드로 정리.
DTO: request for bulk delete
backend/src/main/java/moadong/club/payload/request/ClubApplicantDeleteRequest.java
다건 삭제 요청 DTO(레코드) 추가: @NotEmpty List<String> applicantIds.
Service: bulk delete logic
backend/src/main/java/moadong/club/service/ClubApplyService.java
deleteApplicant 시그니처를 String appIdClubApplicantDeleteRequest request로 변경. findAllByIdInAndQuestionId(request.applicantIds(), clubId)로 조회 후 개수 불일치 시 예외(APPLICANT_NOT_FOUND) 발생, deleteAll로 일괄 삭제.
Repository: finder for bulk
backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java
새 메서드 추가: List<ClubApplication> findAllByIdInAndQuestionId(List<String> ids, String clubId).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Controller as ClubApplyController
    participant Service as ClubApplyService
    participant Repo as ClubApplicationRepository
    participant DB

    Client->>Controller: DELETE /clubs/{clubId}/applicant\nBody: { applicantIds: [...] }
    Controller->>Service: deleteApplicant(clubId, request, user)
    Service->>Repo: findAllByIdInAndQuestionId(request.applicantIds, clubId)
    Repo->>DB: SELECT * WHERE id IN (...) AND question_id = clubId
    DB-->>Repo: applicants[]
    Repo-->>Service: applicants[]
    alt any missing ids
        Service-->>Controller: throw APPLICANT_NOT_FOUND
        Controller-->>Client: 404/400
    else all found
        Service->>Repo: deleteAll(applicants)
        Repo->>DB: DELETE ...
        DB-->>Repo: ok
        Service-->>Controller: void
        Controller-->>Client: 204 No Content
    end
    note over Service,Repo: 검증: 조회 수 == 요청 ID 수
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
다건 삭제 지원: 배열로 지원자 id 입력받음 MOA-196

Assessment against linked issues: Out-of-scope changes

(없음)

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Zepelown
  • seongwon030
  • oesnuj
  • suhyun113

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#696-support-multi-delete-application-MOA-196

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.
    • 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.
  • 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 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/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@github-actions
Copy link

Test Results

75 tests   75 ✅  2s ⏱️
12 suites   0 💤
12 files     0 ❌

Results for commit 5a6bee1.

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

🧹 Nitpick comments (7)
backend/src/main/java/moadong/club/payload/request/ClubApplicantDeleteRequest.java (1)

7-10: 요소 단위 검증 추가 및 스키마 예시 보강 제안

목록 자체에만 @notempty가 적용되어 있어 공백/빈 문자열 ID가 섞여 들어와도 통과합니다. 요소 단위 @notblank, 그리고 대량 요청 방지를 위한 @SiZe 상한을 고려해 주세요. OpenAPI 예시도 함께 추가하면 FE 연동이 수월합니다.

아래와 같이 변경을 제안합니다:

 package moadong.club.payload.request;

+import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Size;
+import io.swagger.v3.oas.annotations.media.Schema;

 import java.util.List;

 public record ClubApplicantDeleteRequest(
-        @NotEmpty
-        List<String> applicantIds
+        @NotEmpty
+        @Size(max = 1000)
+        @Schema(description = "삭제할 지원자 ID 목록", example = "[\"app_1\",\"app_2\"]")
+        List<@NotBlank String> applicantIds
 ) {
 }
backend/src/main/java/moadong/club/repository/ClubApplicationRepository.java (2)

16-16: 메서드 시그니처 파라미터명 혼동 가능성

findAllByIdInAndQuestionId(List<String> ids, String clubId)에서 두 번째 파라미터명 clubId는 실제로는 questionId 필터에 바인딩됩니다. 팀 내 일관성을 위해 파라미터명을 의미에 맞게 정리해 주세요.

-List<ClubApplication> findAllByIdInAndQuestionId(List<String> ids, String clubId);
+List<ClubApplication> findAllByIdInAndQuestionId(List<String> ids, String questionId);

10-17: 단일 DB 오퍼레이션으로의 일괄 삭제 메서드 추가 제안

현재 서비스에서 전체 엔티티를 읽고 나서 deleteAll(applicants)로 삭제합니다. Mongo에서 파생 쿼리 deleteByIdInAndQuestionId(...)를 사용하면 다건 삭제를 단일 오퍼레이션으로 수행해 부분 삭제 가능성을 줄이고 트래픽도 절감합니다. 필요 시 삭제 건수를 반환받아 검증도 가능합니다.

리포지토리에 아래 메서드를 추가하는 것을 권장합니다:

 public interface ClubApplicationRepository extends MongoRepository<ClubApplication, String> {
@@
     Optional<ClubApplication> findByIdAndQuestionId(String id, String questionId);
 
     List<ClubApplication> findAllByIdInAndQuestionId(List<String> ids, String questionId);
+
+    long deleteByIdInAndQuestionId(List<String> ids, String questionId);
+    long countByIdInAndQuestionId(List<String> ids, String questionId);
 }
backend/src/main/java/moadong/club/service/ClubApplyService.java (1)

159-175: 일괄 삭제를 단일 쿼리로 수행하고 사전 검증은 카운트로 대체

엔티티 전체를 읽지 않고 카운트 후 삭제하면 네트워크/메모리 비용이 줄고, Mongo 단일 오퍼레이션으로 부분 실패 가능성도 낮아집니다. 위 리포지토리 보강을 전제로 다음과 같이 개선을 제안합니다.

-    @Transactional
-    public void deleteApplicant(String clubId, ClubApplicantDeleteRequest request, CustomUserDetails user) {
+    @Transactional
+    public void deleteApplicant(String clubId, ClubApplicantDeleteRequest request, CustomUserDetails user) {
         Club club = clubRepository.findById(clubId)
                 .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));
@@
-        List<ClubApplication> applicants = clubApplicationRepository.findAllByIdInAndQuestionId(request.applicantIds(), clubId);
-
-        if (applicants.size() != request.applicantIds().size()) {
-            throw new RestApiException(ErrorCode.APPLICANT_NOT_FOUND);
-        }
-
-        clubApplicationRepository.deleteAll(applicants);
+        List<String> uniqueIds = request.applicantIds().stream().distinct().toList();
+        long found = clubApplicationRepository.countByIdInAndQuestionId(uniqueIds, clubId);
+        if (found != uniqueIds.size()) {
+            throw new RestApiException(ErrorCode.APPLICANT_NOT_FOUND);
+        }
+        clubApplicationRepository.deleteByIdInAndQuestionId(uniqueIds, clubId);
     }

추가로, 대량 요청으로 $in 조건이 비대해질 수 있으므로 상한(예: 1000건)을 DTO/서비스 양측에서 함께 제한하는 것을 권장합니다.

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

85-96: DELETE 요청 바디 사용의 호환성 및 문서/메시지 정교화

  • 일부 클라이언트/프록시는 DELETE 바디를 제한할 수 있습니다. 인프라/게이트웨이에서 DELETE+JSON 바디가 문제없는지 확인해 주세요. 대안으로 POST /applicants:batch-delete 같은 커맨드 엔드포인트도 고려할 수 있습니다.
  • 엔드포인트가 다건 삭제를 수행하므로, 요약/응답 메시지를 복수형으로 명확히 표현하면 좋습니다.
-@DeleteMapping("/applicant")
-@Operation(summary = "지원자 삭제",
-        description = "클럽 지원자의 지원서를 삭제합니다"
+@DeleteMapping("/applicants")
+@Operation(summary = "지원자 여러 명 삭제",
+        description = "요청한 지원자들의 지원서를 일괄 삭제합니다"
 )
@@
-        return Response.ok("success delete applicant");
+        return Response.ok("success delete applicants");

7-7: 와일드카드 임포트 대신 명시 임포트 유지 권장(팀 컨벤션에 따름)

리뷰 난이도와 변경 영향 추적을 위해 DTO 임포트는 명시적으로 두는 편을 권장합니다. 팀 규칙에 따라 결정해 주세요.


70-74: API 문구 정합성 확인

@Operation 설명에 여전히 appId 안내 문구가 남아 있습니다(수정 엔드포인트). 실제로는 PathVariable 그대로 사용 중이므로 현재는 문제 없지만, 삭제 엔드포인트와 혼동되지 않도록 문구를 재점검해 주세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear 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 7bc97f7 and 5a6bee1.

📒 Files selected for processing (4)
  • backend/src/main/java/moadong/club/controller/ClubApplyController.java (3 hunks)
  • backend/src/main/java/moadong/club/payload/request/ClubApplicantDeleteRequest.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)
🧰 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/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/service/ClubApplyService.java
🔇 Additional comments (2)
backend/src/main/java/moadong/club/payload/request/ClubApplicantDeleteRequest.java (1)

7-10: LGTM — 목적에 맞는 간결한 DTO 도입

다건 삭제 요구사항에 맞는 레코드 DTO 추가는 방향성 좋습니다.

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

159-175: 요구사항 확인: 일부 유효/일부 무효 ID 혼합 시 동작 정책

현재 구현은 하나라도 존재하지 않으면 전체 삭제를 중단하고 404를 반환합니다. 부분 삭제 허용 여부가 명확하지 않습니다. FE/기획과 정책을 합의해 주세요. 부분 삭제 허용 시에는 성공/실패 ID 목록을 응답 바디에 포함하는 패턴이 일반적입니다.

Comment on lines +168 to 175
List<ClubApplication> applicants = clubApplicationRepository.findAllByIdInAndQuestionId(request.applicantIds(), clubId);

if (applicants.size() != request.applicantIds().size()) {
throw new RestApiException(ErrorCode.APPLICANT_NOT_FOUND);
}

clubApplicationRepository.delete(application);
clubApplicationRepository.deleteAll(applicants);
}
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

중복 ID 요청 시 오탐 404 발생 — 존재 확인 로직의 크리티컬 버그

applicants.size()request.applicantIds().size()를 직접 비교하면, 요청에 중복 ID가 포함될 때 실제 전부 존재해도 사이즈가 달라져 APPLICANT_NOT_FOUND가 잘못 발생합니다. 중복 제거 후 비교가 필요합니다.

최소 수정안:

-        List<ClubApplication> applicants = clubApplicationRepository.findAllByIdInAndQuestionId(request.applicantIds(), clubId);
-
-        if (applicants.size() != request.applicantIds().size()) {
+        List<String> uniqueIds = request.applicantIds().stream().distinct().toList();
+        List<ClubApplication> applicants = clubApplicationRepository.findAllByIdInAndQuestionId(uniqueIds, clubId);
+
+        if (applicants.size() != uniqueIds.size()) {
             throw new RestApiException(ErrorCode.APPLICANT_NOT_FOUND);
         }
 
         clubApplicationRepository.deleteAll(applicants);
📝 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
List<ClubApplication> applicants = clubApplicationRepository.findAllByIdInAndQuestionId(request.applicantIds(), clubId);
if (applicants.size() != request.applicantIds().size()) {
throw new RestApiException(ErrorCode.APPLICANT_NOT_FOUND);
}
clubApplicationRepository.delete(application);
clubApplicationRepository.deleteAll(applicants);
}
// Deduplicate incoming applicant IDs to avoid false negatives when duplicates are present
List<String> uniqueIds = request.applicantIds().stream()
.distinct()
.toList();
List<ClubApplication> applicants = clubApplicationRepository
.findAllByIdInAndQuestionId(uniqueIds, clubId);
if (applicants.size() != uniqueIds.size()) {
throw new RestApiException(ErrorCode.APPLICANT_NOT_FOUND);
}
clubApplicationRepository.deleteAll(applicants);
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/service/ClubApplyService.java around lines
168 to 175, the existence check wrongly compares applicants.size() to
request.applicantIds().size(), causing false 404s when the request contains
duplicate IDs; fix by deduplicating the requested IDs before querying and
comparing: compute a collection of distinct applicant IDs (e.g.,
stream().distinct() or a Set), use that distinct collection in the repository
call, and then compare applicants.size() against the distinct collection size
(or validate all distinct IDs were found) before deleting.

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.

#698 과 무슨 차이가 있나요?

@lepitaaar
Copy link
Contributor Author

#698 과 무슨 차이가 있나요?

698은 프론트입니당 요건 백엔드 수정

@lepitaaar lepitaaar merged commit 12243dc into develop/be Aug 25, 2025
5 checks passed
@lepitaaar lepitaaar deleted the feature/#696-support-multi-delete-application-MOA-196 branch September 20, 2025 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신 작업 💾 BE Backend 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments