Skip to content

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Dec 23, 2025

BE v1.1.0 릴리즈

  • 새로운 기능

    • 지원 폼 삭제 API 및 내부/외부(formMode + 외부 지원 URL) 지원 방식 추가
    • Presigned URL 기반 이미지 업로드 워크플로우(로고/커버/피드: 업로드 URL 생성·완료) 도입
    • 동아리 설명 구조화(소개/활동/수상/이상형/FAQ) 및 관련 DTO 추가
    • 다중 리프레시 토큰 지원으로 기기별 로그인 관리 강화
  • 개선 사항

    • 검색 키워드 이스케이프 적용으로 검색 안정성 향상
    • 모집 기간을 시작/종료로 분리하고 최종 수정일 표시 추가
    • 오류 코드·검증 메시지 확장으로 예외 처리 명확화
  • 테스트

    • 이미지 업로드·제약 및 최종 수정일 관련 단위테스트 추가/조정

yw6938 and others added 30 commits November 9, 2025 02:59
…pload-MOA-284

[feature] 동아리 활동사진 이미지 다중 업로드 지원하도록 수정한다
@vercel
Copy link

vercel bot commented Dec 23, 2025

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

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Dec 26, 2025 1:03am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 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.
  • You can also validate your configuration using the online YAML validator.
  • 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

S3 Presigned URL 기반 이미지 업로드(S3Presigner 추가), 클럽 상세 설명(ClubDescription) 및 관련 DTO/엔티티 추가, 신청서(INTERNAL/EXTERNAL) 모드·외부 URL 검증, 모집정보 lastModifiedDate 추적, 관리자용 신청서 삭제 엔드포인트 및 다수의 리포지토리/서비스/테스트 변경입니다.

Changes

코호트 / File(s) 변경 요약
빌드/의존성
\backend/build.gradle``
AWS SDK URL connection client 의존성 추가(software.amazon.awssdk:url-connection-client:2.26.0).
S3 설정
\backend/src/main/java/.../S3Config.java``
S3Presigner 빈 추가, 자격증명/엔드포인트 검증 및 destroy 처리 추가.
이미지 업로드 컨트롤러/DTO
\backend/src/main/java/moadong/media/controller/ClubImageController.java`, `backend/src/main/java/moadong/media/dto/UploadUrlRequest.java`, `.../UploadCompleteRequest.java`, `.../PresignedUploadResponse.java``
기존 multipart 엔드포인트 제거, 프리사인드 URL 생성/완료 엔드포인트 및 DTO(UploadUrlRequest, UploadCompleteRequest, PresignedUploadResponse) 추가, @CurrentUser 적용.
이미지 서비스(구현/인터페이스)
\backend/src/main/java/moadong/media/service/CloudflareImageService.java`, `.../ClubImageService.java``
presign 생성/완료 메서드(사용자 ID 포함) 및 배치 feed presign 추가, 파일명/URL 검사, delete/complete 로직·트랜잭션·예외 매핑, 인터페이스 시그니처 변경.
클럽 설명·구성요소
\backend/src/main/java/moadong/club/entity/ClubDescription.java`, `.../ClubAward.java`, `.../ClubIdealCandidate.java``
ClubDescription, ClubAward, ClubIdealCandidate 엔티티 추가.
Club 엔티티·DTO 변경
\backend/src/main/java/moadong/club/entity/Club.java`, `.../payload/dto/ClubDetailedResult.java`, `.../payload/request/ClubInfoRequest.java``
Club에 ClubDescription 필드 추가 및 생성자/빌더 반영, ClubDetailedResult에 recruitmentStart/recruitmentEnd/lastModifiedDate 추가, ClubInfoRequest에 description 필드 추가.
모집정보 타입 변경
\backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java`, `.../payload/request/ClubRecruitmentInfoUpdateRequest.java``
recruitmentStart/End 타입을 Instant로 변경; lastModifiedDate 및 updateLastModifiedDate() 추가; description/faqs 제거.
신청서(폼) 관련
\backend/src/main/java/moadong/club/entity/ClubApplicationForm.java`, `.../enums/ApplicationFormMode.java`, `.../payload/request/ClubApplicationFormCreateRequest.java`, `.../EditRequest.java`, `.../response/ClubApplicationFormResponse.java``
ApplicationFormMode 추가(INTERNAL/EXTERNAL), externalApplicationUrl 화이트리스트 검증 추가, 요청/응답에 formMode·externalApplicationUrl 필드 추가, CreateRequest에 모드별 검증(@AssertTrue) 도입.
관리자 신청서 삭제 / SSE
\backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java`, `.../service/ClubApplyAdminService.java`, `.../repository/ClubApplicantsRepository.java``
DELETE /application/{id} 엔드포인트 추가(컨트롤러·서비스), SSE 핸들러에 HttpServletResponse 파라미터로 버퍼링/캐시 헤더 설정, repository에 deleteAllByFormId 추가, 중복 질문 아이템 검증 추가.
검색·프로필·상태 계산
\backend/src/main/java/moadong/club/service/ClubSearchService.java`, `.../ClubProfileService.java`, `.../util/RecruitmentStateCalculator.java`, `.../controller/ClubSearchController.java``
검색어 이스케이프(Pattern.quote) 적용, ClubProfileService에서 updateLastModifiedDate() 호출 추가, RecruitmentStateCalculator의 OPEN/ALWAYS 엣지케이스 로직 조정, Swagger 주석 미세수정.
리포지토리·FCM
\backend/src/main/java/moadong/club/repository/ClubSearchRepository.java`, `.../ClubApplicantsRepository.java`, `backend/src/main/java/moadong/fcm/repository/FcmTokenRepository.java``
searchClubsByKeyword의 OR 필드 조정(category 포함), searchRecommendClubs 제거, deleteAllByFormId 추가, deleteFcmTokenByToken 추가.
에러 코드
\backend/src/main/java/moadong/global/exception/ErrorCode.java``
클럽/파일/토큰/신청 관련 신규 ErrorCode 다수 추가(예: NOT_ALLOWED_EXTERNAL_URL, DUPLICATE_QUESTIONS_ITEMS, TOKEN_INVALID 등).
유저 토큰 모델
\backend/src/main/java/moadong/user/entity/User.java`, `.../repository/UserRepository.java`, `.../service/UserCommandService.java``
RefreshToken 단일 필드 → List로 변경, add/replace/removeAll 토큰 관리 메서드 추가, 사용자 리포지토리 쿼리명 변경, auth 흐름에서 토큰 컬렉션 기반 로직 적용.
테스트·픽스처
\backend/src/test/java/.../*``
lastModifiedDate 관련 단위테스트 추가, Cloudflare 이미지 서비스 관련 테스트 추가/삭제/수정, 픽스처 및 테스트 시그니처 업데이트(Instant 등).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as ClubImageController
    participant Service as CloudflareImageService
    participant Repo as ClubRepository
    participant Presigner as S3Presigner
    participant S3 as Cloudflare S3

    rect rgb(220,240,255)
    Note over Client,Controller: 프리사인드 URL 생성
    Client->>Controller: POST /generate-logo-url (clubId, fileName, contentType)
    Controller->>Service: generateLogoUploadUrl(clubId, userId, fileName, contentType)
    Service->>Repo: findClubById(clubId)
    Repo-->>Service: Club
    Service->>Service: validateFileName()/validateContentType()
    Service->>Presigner: presign PutObjectRequest
    Presigner-->>Service: presignedUrl + headers
    Service-->>Controller: PresignedUploadResponse
    Controller-->>Client: 200 {presignedUrl, finalUrl, headers}
    end

    rect rgb(240,255,220)
    Note over Client,S3: 클라이언트가 직접 업로드
    Client->>S3: PUT presignedUrl (이미지)
    S3-->>Client: 200 OK
    end

    rect rgb(255,240,220)
    Note over Client,Controller: 업로드 완료 통지
    Client->>Controller: POST /complete-logo-upload (clubId, fileUrl)
    Controller->>Service: completeLogoUpload(clubId, userId, fileUrl)
    Service->>Service: extractKey()/validateUrl()
    Service->>Repo: save(updated Club)
    Repo-->>Service: 저장 완료
    Service-->>Controller: 200 OK
    Controller-->>Client: 200 OK
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • yw6938
  • Zepelown
  • PororoAndFriends

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive 제목이 너무 모호하고 일반적이어서 실제 변경 사항을 명확히 파악하기 어렵습니다. 더 구체적인 제목으로 변경하세요. 예: '[release] BE: 다중 refresh token 지원 및 이미지 업로드 개선'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • 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

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

@seongwon030 seongwon030 added 💾 BE Backend 📈 release 릴리즈 배포 labels Dec 23, 2025
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

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

8-15: 필드 검증 추가를 고려하세요.

recruitmentStartrecruitmentEnd 필드에 @NotNull 등의 검증 어노테이션이 없습니다. 필수 필드인 경우 요청 검증을 추가하여 잘못된 입력을 조기에 차단하는 것이 좋습니다.

🔎 제안하는 수정 사항
+import jakarta.validation.constraints.NotNull;
+
 public record ClubRecruitmentInfoUpdateRequest(
+    @NotNull
     Instant recruitmentStart,
+    @NotNull
     Instant recruitmentEnd,
     String recruitmentTarget,
     String description,
     String externalApplicationUrl,
     List<Faq> faqs
 ) {
backend/src/main/java/moadong/club/enums/ApplicationFormMode.java (1)

1-6: JavaDoc 추가를 고려하세요.

Enum 상수의 이름이 명확하지만, 각 모드의 사용 시나리오를 설명하는 JavaDoc을 추가하면 코드 가독성과 유지보수성이 향상됩니다.

🔎 제안하는 문서화
 package moadong.club.enums;
 
+/**
+ * 동아리 지원서 양식 모드
+ */
 public enum ApplicationFormMode {
+    /** 내부 지원서 양식 (자체 질문 항목 사용) */
     INTERNAL,
+    /** 외부 지원서 양식 (외부 URL 사용) */
     EXTERNAL
 }
backend/src/main/java/moadong/media/dto/PresignedUploadResponse.java (1)

5-12: requiredHeaders Map의 불변성을 고려해 주세요.

record의 필드는 final이지만, Map<String, String> 타입의 requiredHeaders는 외부에서 수정될 수 있습니다. 생성 시점에 Map.copyOf() 또는 Collections.unmodifiableMap()으로 래핑하는 것을 권장합니다.

🔎 불변 Map 적용 예시
+import java.util.Map;
+
 public record PresignedUploadResponse(
     String presignedUrl, 
     String finalUrl,
     Map<String, String> requiredHeaders,
     boolean success,
     String failureReason
 ) {
+    public PresignedUploadResponse {
+        requiredHeaders = requiredHeaders != null ? Map.copyOf(requiredHeaders) : Map.of();
+    }
 }
backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java (1)

59-105: 주석 처리된 테스트 코드를 정리해 주세요.

주석 처리된 두 개의 테스트가 있습니다. 이 테스트들은 Pattern.quote() 변경사항이 반영되지 않아 활성화하면 실패합니다. 다음 중 하나를 권장합니다:

  1. Pattern.quote(keyword)를 적용하여 테스트를 복원
  2. 더 이상 필요하지 않다면 테스트 코드 삭제
backend/src/test/java/moadong/fixture/ClubRequestFixture.java (1)

8-10: 사용되지 않는 import 제거 필요

LocalDateTime이 import되었지만 실제로 사용되지 않습니다. InstantChronoUnit만 사용되고 있으므로 불필요한 import를 제거하세요.

🔎 제안된 수정
 import java.time.Instant;
-import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
backend/src/test/java/moadong/media/service/CloudflareImageServiceUpdateFeedsLimitTest.java (1)

59-75: 테스트 이름과 실제 동작 불일치

테스트 이름이 "MAX_FEED_COUNT_이상의"로 되어 있지만, 실제로는 정확히 15개(MAX_FEED_COUNT와 동일)로만 테스트하고 있습니다. 경계값 테스트의 명확성을 위해 이름을 수정하거나, 16개 케이스도 추가하는 것을 고려하세요.

🔎 테스트 이름 수정 제안
 @Test
-void MAX_FEED_COUNT_이상의_피드를_업로드하면_TOO_MANY_FILES를_반환한다() {
+void MAX_FEED_COUNT와_같은_피드를_업로드하면_TOO_MANY_FILES를_반환한다() {
backend/src/main/java/moadong/media/util/S3Config.java (1)

50-57: isBlank() 사용 권장

공백만 있는 문자열도 유효하지 않은 설정값으로 처리되어야 합니다. isEmpty() 대신 isBlank()를 사용하면 공백 문자만 포함된 경우도 검증할 수 있습니다.

🔎 제안된 수정
 private void validateCredentials() {
-    if (accessKey == null || accessKey.isEmpty() || secretKey == null || secretKey.isEmpty()) {
+    if (accessKey == null || accessKey.isBlank() || secretKey == null || secretKey.isBlank()) {
         throw new IllegalStateException("AWS credentials (accessKey, secretKey) must be configured");
     }
-    if (endpoint == null || endpoint.isEmpty()) {
+    if (endpoint == null || endpoint.isBlank()) {
         throw new IllegalStateException("AWS S3 endpoint must be configured");
     }
 }
backend/src/test/java/moadong/media/service/CloudFlareImageServiceTest.java (1)

77-98: 테스트 메서드 이름의 공백 제거 권장

테스트 메서드 이름에 공백이 포함되어 있습니다 (예: 잘못된_filePath_일때_deleteFile_을_실패한다 ()). 일관성을 위해 공백을 제거하거나 언더스코어로 대체하는 것이 좋습니다.

🔎 제안된 수정
 @Test
-void 잘못된_filePath_일때_deleteFile_을_실패한다 (){
+void 잘못된_filePath_일때_deleteFile_을_실패한다(){
     //given
     ...
 }

 @Test
-void  접두_불일치면_deleteFile_을_실패한다 (){
+void 접두_불일치면_deleteFile_을_실패한다(){
     //given
     ...
 }

 @Test
-void  정상_url이면_삭제_성공 () {
+void 정상_url이면_삭제_성공(){
     //given
     ...
 }
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java (1)

76-77: EXTERNAL 모드에 더 명확한 에러 코드 사용 권장

APPLICATION_NOT_FOUND는 폼이 존재하지 않는 경우를 의미하지만, 여기서는 폼이 존재하되 외부 지원만 가능한 상황입니다. 클라이언트가 올바른 조치를 취할 수 있도록 더 명확한 에러 코드(예: EXTERNAL_APPLICATION_ONLY 또는 INTERNAL_APPLICATION_NOT_ALLOWED)를 사용하는 것이 좋습니다.

🔎 제안된 수정
         if (clubApplicationForm.getFormMode() == ApplicationFormMode.EXTERNAL)
-            throw new RestApiException(ErrorCode.APPLICATION_NOT_FOUND);
+            throw new RestApiException(ErrorCode.EXTERNAL_APPLICATION_ONLY);

ErrorCode enum에 새로운 코드 추가가 필요합니다:

EXTERNAL_APPLICATION_ONLY(HttpStatus.BAD_REQUEST, "외부 지원 링크를 통해서만 지원 가능합니다."),
backend/src/main/java/moadong/media/dto/UploadUrlRequest.java (1)

10-10: MIME 타입 패턴에서 비표준 타입 제거 권장.

정규식 패턴에 jpg가 포함되어 있지만, image/jpg는 비표준 MIME 타입입니다. 표준 MIME 타입은 image/jpeg입니다. 클라이언트가 비표준 타입을 전송할 가능성이 있다면 유지해도 되지만, 그렇지 않다면 jpg를 제거하는 것이 더 엄격한 검증이 될 수 있습니다.

🔎 제안하는 수정사항
-        @Pattern(regexp = "^image/(jpeg|jpg|png|gif|bmp|webp)$", message = "지원되지 않는 MIME 타입입니다.")
+        @Pattern(regexp = "^image/(jpeg|png|gif|bmp|webp)$", message = "지원되지 않는 MIME 타입입니다.")
backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (1)

40-52: DateTimeFormatter 중복 생성 제거 권장.

동일한 패턴("yyyy.MM.dd HH:mm")으로 DateTimeFormatter를 두 번 생성하고 있습니다. 가독성과 성능을 위해 한 번만 생성하여 재사용하는 것이 좋습니다.

🔎 제안하는 수정사항
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm");
+
         String start = "미정";
         String end = "미정";
         if (clubRecruitmentInformation.hasRecruitmentPeriod()) {
-            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm");
             start = clubRecruitmentInformation.getRecruitmentStart().format(formatter);
             end = clubRecruitmentInformation.getRecruitmentEnd().format(formatter);
         }

         String lastModifiedDate = "";
         if (club.getClubRecruitmentInformation().getLastModifiedDate() != null) {
-            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm");
             lastModifiedDate = club.getClubRecruitmentInformation().getLastModifiedDate().format(formatter);
         }
backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java (2)

18-18: 미사용 어노테이션 제거 필요.

@Spy 어노테이션이 선언되어 있지만 실제로 사용되지 않습니다. 불필요한 코드는 제거하는 것이 좋습니다.

🔎 제안하는 수정사항
     @Mock
-    @Spy
     ClubSearchRepository clubSearchRepository;

또는 @Spy가 필요하다면 @Mock 대신 사용해야 합니다.


68-72: 시간 기반 테스트의 안정성 고려 필요.

현재 테스트는 lastModifiedDate가 현재 시간으로부터 ±1초 이내인지 검증합니다. 이는 대부분의 경우 동작하지만, CI 환경이 느리거나 시스템 부하가 높을 때 가끔 실패할 수 있습니다. 더 관대한 시간 범위(예: ±5초)를 고려하거나, Clock을 모킹하는 방식으로 테스트를 더 안정적으로 만드는 것을 권장합니다.

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

18-18: 미사용 import 제거를 고려하세요.

lombok.Setter import가 추가되었지만, 클래스에 @Setter 어노테이션이 사용되지 않고 있습니다. setLastModifiedDate는 직접 정의된 private 메서드입니다.

🔎 제안된 수정
-import lombok.Setter;

95-109: 타임존 상수 추출을 고려하세요.

ZoneId.of("Asia/Seoul")가 두 메서드에서 중복 사용되고 있습니다. 클래스 레벨 상수로 추출하면 유지보수성이 향상됩니다.

🔎 제안된 수정
+    private static final ZoneId SEOUL_ZONE = ZoneId.of("Asia/Seoul");
+
     public ZonedDateTime getRecruitmentStart() {
-        ZoneId seoulZone = ZoneId.of("Asia/Seoul");
         if (recruitmentStart == null) {
             return null;
         }
-        return recruitmentStart.atZone(seoulZone);
+        return recruitmentStart.atZone(SEOUL_ZONE);
     }

     public ZonedDateTime getRecruitmentEnd() {
-        ZoneId seoulZone = ZoneId.of("Asia/Seoul");
         if (recruitmentEnd == null) {
             return null;
         }
-        return recruitmentEnd.atZone(seoulZone);
+        return recruitmentEnd.atZone(SEOUL_ZONE);
     }
backend/src/main/java/moadong/media/service/CloudflareImageService.java (2)

174-203: 배치 응답에서 성공/실패가 혼합되는 패턴에 대해 클라이언트 처리 확인이 필요합니다.

각 요청별로 성공 또는 에러 응답이 혼합된 리스트를 반환합니다. 클라이언트가 각 PresignedUploadResponsesuccess 필드를 확인하여 개별 처리해야 합니다.

또한 Lines 182, 186에서 java.util.List.of(), java.util.ArrayList를 인라인으로 사용하고 있는데, 상단 import와 일관성을 위해 정리를 고려하세요.

🔎 import 정리 제안
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.ArrayList;
+import java.util.Collections;

그리고 해당 라인들에서:

-            return java.util.List.of(errorResponse(ErrorCode.TOO_MANY_FILES));
+            return List.of(errorResponse(ErrorCode.TOO_MANY_FILES));
-        java.util.ArrayList<PresignedUploadResponse> results = new java.util.ArrayList<>(limit + 1);
+        ArrayList<PresignedUploadResponse> results = new ArrayList<>(limit + 1);

327-334: 에러 코드 선택에 대해 검토하세요.

빈 파일명에 대해 FILE_NOT_FOUND를 사용하는 것은 의미상 INVALID_FILE_NAME이 더 적절할 수 있습니다. 다만 기존 에러 코드 체계와의 일관성이 중요하다면 현재 구현도 수용 가능합니다.

📜 Review details

Configuration used: Organization 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 34692b5 and 5c182ca.

📒 Files selected for processing (39)
  • backend/build.gradle
  • backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
  • backend/src/main/java/moadong/club/controller/ClubApplyPublicController.java
  • backend/src/main/java/moadong/club/controller/ClubSearchController.java
  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/main/java/moadong/club/entity/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java
  • backend/src/main/java/moadong/club/enums/ApplicationFormMode.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicationFormCreateRequest.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicationFormEditRequest.java
  • backend/src/main/java/moadong/club/payload/request/ClubRecruitmentInfoUpdateRequest.java
  • backend/src/main/java/moadong/club/payload/response/ClubApplicationFormResponse.java
  • backend/src/main/java/moadong/club/repository/ClubApplicantsRepository.java
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
  • backend/src/main/java/moadong/club/service/ClubApplyAdminService.java
  • backend/src/main/java/moadong/club/service/ClubApplyPublicService.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/service/ClubSearchService.java
  • backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java
  • backend/src/main/java/moadong/fcm/repository/FcmTokenRepository.java
  • backend/src/main/java/moadong/global/exception/ErrorCode.java
  • backend/src/main/java/moadong/media/controller/ClubImageController.java
  • backend/src/main/java/moadong/media/dto/FeedUpdateRequest.java
  • backend/src/main/java/moadong/media/dto/PresignedUploadResponse.java
  • backend/src/main/java/moadong/media/dto/UploadCompleteRequest.java
  • backend/src/main/java/moadong/media/dto/UploadUrlRequest.java
  • backend/src/main/java/moadong/media/service/CloudflareImageService.java
  • backend/src/main/java/moadong/media/service/ClubImageService.java
  • backend/src/main/java/moadong/media/util/S3Config.java
  • backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java
  • backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java
  • backend/src/test/java/moadong/fcm/service/FcmServiceTest.java
  • backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
  • backend/src/test/java/moadong/media/service/CloudFlareImageServiceTest.java
  • backend/src/test/java/moadong/media/service/CloudflareClubImageServiceFeedTest.java
  • backend/src/test/java/moadong/media/service/CloudflareClubImageServiceLogoTest.java
  • backend/src/test/java/moadong/media/service/CloudflareImageServiceUpdateFeedsLimitTest.java
💤 Files with no reviewable changes (3)
  • backend/src/test/java/moadong/media/service/CloudflareClubImageServiceLogoTest.java
  • backend/src/test/java/moadong/media/service/CloudflareClubImageServiceFeedTest.java
  • backend/src/main/java/moadong/club/entity/Club.java
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 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/test/java/moadong/club/service/ClubSearchServiceTest.java
  • backend/src/main/java/moadong/club/repository/ClubApplicantsRepository.java
  • backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java
  • backend/src/main/java/moadong/club/service/ClubApplyPublicService.java
  • backend/src/main/java/moadong/club/service/ClubSearchService.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/entity/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
  • backend/src/main/java/moadong/club/service/ClubApplyAdminService.java
  • backend/src/main/java/moadong/club/payload/response/ClubApplicationFormResponse.java
📚 Learning: 2025-05-01T08:32:09.398Z
Learnt from: Due-IT
Repo: Moadong/moadong PR: 329
File: backend/src/main/java/moadong/club/controller/ClubSearchController.java:0-0
Timestamp: 2025-05-01T08:32:09.398Z
Learning: ClubSearchController의 searchClubsByKeyword 메서드와 ClubSearchService의 searchClubsByKeyword 메서드 사이에 파라미터 순서 일치가 중요하다. 현재 컨트롤러의 매개변수 순서는 'keyword, recruitmentStatus, category, division'인 반면, 서비스 메서드의 순서는 'keyword, recruitmentStatus, division, category'이다. 컨트롤러에서 서비스 호출 시 'keyword, recruitmentStatus, division, category' 순서로 전달하면 category와 division 값이 바뀌어 검색 결과가 잘못될 수 있다.

Applied to files:

  • backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java
  • backend/src/main/java/moadong/club/controller/ClubSearchController.java
  • backend/src/main/java/moadong/club/service/ClubSearchService.java
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.

Applied to files:

  • backend/src/main/java/moadong/club/repository/ClubApplicantsRepository.java
  • backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java
  • backend/src/main/java/moadong/club/enums/ApplicationFormMode.java
  • backend/src/main/java/moadong/club/service/ClubApplyPublicService.java
  • backend/src/main/java/moadong/club/service/ClubSearchService.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/entity/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/controller/ClubApplyPublicController.java
  • backend/src/main/java/moadong/club/service/ClubApplyAdminService.java
  • backend/src/main/java/moadong/club/payload/response/ClubApplicationFormResponse.java
  • backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java
  • backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicationFormCreateRequest.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicationFormEditRequest.java
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 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/repository/ClubApplicantsRepository.java
  • backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java
  • backend/src/main/java/moadong/club/service/ClubApplyPublicService.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/entity/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/controller/ClubApplyPublicController.java
  • backend/src/main/java/moadong/club/service/ClubApplyAdminService.java
  • backend/src/main/java/moadong/club/payload/response/ClubApplicationFormResponse.java
  • backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicationFormCreateRequest.java
  • backend/src/main/java/moadong/club/payload/request/ClubApplicationFormEditRequest.java
📚 Learning: 2025-05-15T12:03:57.356Z
Learnt from: Zepelown
Repo: Moadong/moadong PR: 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/ClubApplyPublicService.java
🧬 Code graph analysis (2)
backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java (3)
backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)
  • RecruitmentStateCalculator (15-78)
backend/src/test/java/moadong/fixture/ClubRequestFixture.java (1)
  • ClubRequestFixture (14-66)
backend/src/test/java/moadong/fixture/UserFixture.java (1)
  • UserFixture (9-33)
backend/src/main/java/moadong/media/service/CloudflareImageService.java (2)
backend/src/main/java/moadong/club/entity/Club.java (1)
  • Slf4j (25-149)
backend/src/main/java/moadong/global/util/ObjectIdConverter.java (1)
  • ObjectIdConverter (7-17)
🔇 Additional comments (36)
backend/src/main/java/moadong/global/exception/ErrorCode.java (1)

8-66: 잘 구조화된 에러 코드 확장입니다.

새로운 에러 코드들이 일관된 패턴과 적절한 HTTP 상태 코드를 사용하여 잘 추가되었습니다. 에러 메시지도 사용자 친화적이며 명확합니다.

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

25-53: 문서화 개선이 적절합니다.

Swagger 문서에 줄바꿈 태그를 추가하여 가독성이 향상되었습니다. 컨트롤러와 서비스 간 매개변수 순서도 올바르게 일치합니다.

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

37-41: LGTM!

코드 포맷팅 개선입니다.

backend/src/test/java/moadong/fcm/service/FcmServiceTest.java (1)

67-82: 테스트 격리 개선이 적절합니다.

각 테스트 실행 전에 기존 토큰을 삭제하여 테스트 간 간섭을 방지하고 있습니다. 이는 테스트 안정성을 향상시키는 좋은 관행입니다.

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

38-50: lastModifiedDate 업데이트 로직이 적절합니다.

모집 정보 업데이트 시 updateLastModifiedDate()를 호출하여 수정 시간을 추적하는 것은 좋은 접근입니다. 상태 계산 후, 저장 전에 호출되는 위치도 적절합니다.

backend/src/main/java/moadong/media/dto/UploadCompleteRequest.java (1)

5-9: LGTM!

간결하고 적절한 DTO 설계입니다. @NotBlank 어노테이션이 null 및 빈 문자열을 적절히 처리합니다. 보안 강화를 위해 URL 형식 검증(@Pattern 또는 @URL)을 추가하는 것도 고려해 볼 수 있습니다.

backend/src/test/java/moadong/club/service/ClubSearchServiceTest.java (1)

48-49: LGTM!

테스트가 프로덕션 코드의 Pattern.quote() 동작을 올바르게 반영합니다. 특수문자가 포함된 키워드에서 regex 인젝션을 방지하는 적절한 변경입니다.

backend/src/main/java/moadong/club/repository/ClubSearchRepository.java (1)

44-48: 검색 범위 변경이 의도된 것인지 확인해 주세요.

키워드 검색 대상 필드가 recruitmentInformation.introduction, recruitmentInformation.description에서 category로 변경되었습니다. 이는 검색 동작에 상당한 영향을 미칩니다:

  • 이전: 동아리 소개글/설명에서 키워드 검색 가능
  • 이후: 카테고리명에서만 키워드 검색 가능

사용자가 소개글 내용으로 검색하던 기존 동작이 변경되므로, 이 변경이 의도된 것인지 확인 부탁드립니다.

backend/src/main/java/moadong/club/repository/ClubApplicantsRepository.java (1)

13-14: LGTM!

Spring Data MongoDB 명명 규칙을 따르는 적절한 삭제 메서드입니다. ClubApplyAdminService.deleteClubApplicationForm에서 @Transactional과 함께 사용되어 일관성 있는 삭제 처리가 가능합니다.

backend/src/main/java/moadong/fcm/repository/FcmTokenRepository.java (1)

10-10: LGTM!

기존 findFcmTokenByToken을 보완하는 적절한 삭제 메서드입니다. 토큰 기반 삭제는 로그아웃 및 토큰 갱신 시나리오에 유용합니다.

backend/src/main/java/moadong/media/dto/FeedUpdateRequest.java (1)

6-9: LGTM!

@NotNull 어노테이션이 적절하게 적용되었습니다. 빈 리스트([])는 허용되며, 이는 피드를 모두 삭제하는 유효한 사용 사례입니다. 추가 제약이 필요하다면 @NotEmpty@Size를 고려해 볼 수 있습니다.

backend/src/test/java/moadong/fixture/ClubRequestFixture.java (1)

29-38: LGTM!

defaultRequest() 메서드가 Instant 기반 타임스탬프를 올바르게 사용하고 있으며, 테스트에 필요한 합리적인 기본값을 제공합니다.

backend/src/main/java/moadong/club/payload/response/ClubApplicationFormResponse.java (1)

12-21: LGTM!

externalApplicationUrlformMode 필드가 적절하게 추가되었습니다. INTERNAL/EXTERNAL 모드 구분을 위한 응답 구조가 잘 설계되었습니다.

backend/src/main/java/moadong/media/util/S3Config.java (1)

39-48: LGTM!

S3Presigner 빈이 적절하게 구성되었습니다. destroyMethod = "close"를 통한 리소스 정리와 자격 증명 검증 로직이 잘 구현되었습니다.

backend/src/test/java/moadong/fixture/ClubApplicationEditFixture.java (1)

19-28: LGTM!

테스트 픽스처가 새로운 ClubApplicationFormEditRequest 생성자에 맞게 올바르게 업데이트되었습니다. INTERNAL 모드에서 빈 문자열을 externalApplicationUrl로 사용하는 것이 적절합니다.

backend/src/test/java/moadong/media/service/CloudFlareImageServiceTest.java (2)

44-49: LGTM!

ReflectionTestUtils를 사용한 테스트 셋업이 적절합니다. private 필드 설정과 init() 메서드 호출을 통해 서비스 인스턴스를 올바르게 초기화하고 있습니다.


100-115: LGTM!

ArgumentCaptor를 사용하여 S3 삭제 요청의 bucket과 key 값을 정확하게 검증하고 있습니다. 정상적인 URL 삭제 시나리오에 대한 테스트가 잘 작성되었습니다.

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

58-68: LGTM!

ClubApplicationFormResponse 빌더에 formModeexternalApplicationUrl 필드가 적절하게 추가되었습니다. 새로운 응답 구조가 잘 구현되었습니다.

backend/src/main/java/moadong/club/service/ClubApplyAdminService.java (3)

228-231: null 체크 로직 승인.

questionsexternalApplicationUrl에 대한 null 체크를 추가하여 불필요한 업데이트를 방지하는 것은 좋은 방어적 프로그래밍입니다.


122-129: 삭제 메서드 구현 확인 완료.

지원자 데이터를 먼저 삭제한 후 지원서 양식을 삭제하는 순서가 올바릅니다. 트랜잭션 처리도 적절하게 적용되어 있습니다.


273-274: 이 부분은 리뷰 주석이 필요하지 않습니다.

QuestionItem은 record 타입으로 정의되어 있으며, Java record는 자동으로 equals()hashCode() 메서드를 생성합니다. 따라서 HashSet을 통한 중복 검증이 올바르게 동작하며, 추가 구현이 필요하지 않습니다.

Likely an incorrect or invalid review comment.

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

67-69: 키워드 이스케이핑 구현 승인.

Pattern.quote()를 사용하여 정규식 특수 문자를 이스케이프 처리하는 것은 검색 기능에서 정규식 인젝션을 방지하는 올바른 접근입니다. 공백 제거(trim) 처리도 적절합니다.

backend/src/main/java/moadong/club/payload/request/ClubApplicationFormCreateRequest.java (2)

36-46: INTERNAL 모드 검증 로직 확인 완료.

INTERNAL 모드에서 descriptionquestions 필드가 모두 존재하는지 확인하는 검증 로직이 올바르게 구현되어 있습니다. StringUtils.hasText()와 null/empty 체크를 통해 적절하게 검증하고 있습니다.


48-55: EXTERNAL 모드 검증 로직 확인 완료.

EXTERNAL 모드에서 외부 링크가 필수로 존재해야 한다는 검증이 명확하게 구현되어 있습니다.

backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java (2)

127-128: SSE 버퍼링 방지 헤더 추가 확인 완료.

X-Accel-Buffering: noCache-Control: no-cache 헤더를 추가하여 프록시 서버(특히 Nginx)에서 SSE 응답이 버퍼링되지 않도록 설정한 것은 올바른 구현입니다. 이는 실시간 이벤트 전송에 필수적입니다.


73-81: 삭제 엔드포인트 구현 확인 완료.

지원서 양식 삭제 엔드포인트가 적절하게 구현되어 있습니다. 인증 처리와 권한 검증도 올바르게 적용되어 있습니다.

backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java (1)

46-74: 테스트 구조 확인 완료.

RecruitmentStateCalculator를 static mock으로 우회하고 lastModifiedDate 업데이트를 검증하는 테스트 로직이 명확하게 구현되어 있습니다. 테스트의 목적과 구현이 일치합니다.

backend/src/main/java/moadong/media/service/ClubImageService.java (1)

17-27: LGTM! Presigned URL 워크플로우를 위한 인터페이스가 잘 정의되었습니다.

로고, 피드, 커버 이미지에 대한 presigned URL 생성 및 업로드 완료 메서드가 명확하게 분리되어 있습니다. 피드 업로드 완료 API가 없는 것은 의도된 설계로 보입니다 (컨트롤러 주석에서 updateFeeds에서 검증 수행한다고 명시).

backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java (1)

130-136: 구현이 적절합니다.

private setter와 public update 메서드 패턴이 캡슐화를 잘 유지하고 있습니다. 다만 LocalDateTime.now()는 시스템 기본 타임존을 사용하므로, 분산 환경에서 일관성이 필요하다면 명시적 타임존 지정을 고려할 수 있습니다.

backend/src/main/java/moadong/media/controller/ClubImageController.java (1)

83-83: 주석 대신 @Deprecated 어노테이션 사용을 고려하세요.

API가 더 이상 사용되지 않는다면, 주석보다 @Deprecated 어노테이션을 사용하여 명시적으로 표시하는 것이 좋습니다. 다만 현재는 API가 완전히 제거된 상태이므로, 이 주석은 히스토리 참고용으로 적절합니다.

backend/src/main/java/moadong/media/service/CloudflareImageService.java (6)

60-67: LGTM! 시작 시 설정 검증이 잘 되어있습니다.

@PostConstruct에서 필수 설정값 검증과 정규화를 수행하는 것은 좋은 패턴입니다. 빈 문자열과 후행 슬래시 처리가 적절합니다.


84-110: 로직은 적절하나 들여쓰기가 일관되지 않습니다.

업데이트 로직이 올바릅니다 - null 체크, 개수 제한, URL 검증, 고아 이미지 삭제 순서가 적절합니다. 다만 Lines 85-86에서 탭 문자가 사용된 것으로 보이는데, 프로젝트의 코딩 컨벤션에 따라 스페이스로 통일하는 것이 좋을 수 있습니다.


144-151: 삭제 실패 시 조용히 진행하는 것이 의도적인지 확인하세요.

S3 삭제 실패 시 로그만 남기고 계속 진행하는 것은 graceful degradation 패턴입니다. 그러나 권한 문제나 네트워크 장애 같은 실제 문제가 있을 때도 에러가 숨겨질 수 있습니다. 이 동작이 의도된 것인지, 아니면 특정 에러 타입(예: NoSuchKeyException)만 무시해야 하는지 검토해 주세요.


242-275: 파일 제약 검증 로직이 잘 구현되어 있습니다.

URL 길이, key prefix, 파일 크기 검증이 적절합니다. 특히 clubId/fileType/ prefix 검증으로 다른 클럽의 파일 URL을 사용하는 것을 방지합니다.

한 가지 고려사항: HEAD 요청과 실제 사용 사이에 파일이 삭제될 수 있는 TOCTOU(Time-of-check to time-of-use) 가능성이 있지만, presigned URL 워크플로우에서는 일반적으로 허용되는 수준입니다.


289-322: LGTM! Presigned URL 생성 로직이 잘 구현되어 있습니다.

랜덤 파일명 생성으로 충돌을 방지하고, 설정 가능한 만료 시간, 클라이언트에 필요한 헤더 정보 제공 등이 적절합니다.


70-71: MongoDB 트랜잭션을 위한 배포 환경 설정 확인 필요

MongoTransactionManagerMongoConfig.java에 올바르게 구성되어 있습니다. 다만 MongoDB 트랜잭션이 작동하려면 배포 환경에서 replica set 또는 sharded cluster가 활성화되어 있어야 합니다. 현재 MongoDB 배포 환경에서 replica set이 구성되어 있는지 확인해 주세요. 구성되지 않으면 트랜잭션이 무시될 수 있습니다.

Comment on lines 54 to +57
// S3
implementation 'software.amazon.awssdk:s3:2.26.0'
implementation 'software.amazon.awssdk:auth:2.26.0'
implementation 'software.amazon.awssdk:url-connection-client:2.26.0'
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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

AWS SDK for Java v2 latest stable version 2024 2025

💡 Result:

The latest stable AWS SDK for Java v2 is 2.40.13 (released Dec 20, 2025). (mvnrepository.com)

Citations:


🌐 Web query:

AWS SDK for Java v2 2.26.0 security advisory vulnerabilities

💡 Result:

Short answer — AWS SDK for Java v2 2.26.0 itself has no separate AWS security advisory page, but several known CVEs are reported for that v2.26.0 release (mostly coming from bundled/dependent libraries). The main items flagged by public dependency trackers and the aws-sdk-java-v2 repo are:

  • Netty-related TLS/SSLEngine vulnerability (reported as CVE-2025-24970 affecting netty-handler used by netty-nio-client). [1]
  • Various dependency CVEs surfaced by Maven/registry listings for 2.26.0 modules (examples shown include CVE-2024-47554 and CVE-2025-48924 listed on mvnrepository pages for 2.26.0 artifacts). [2][3][4]

Recommendation: upgrade to a current patched AWS SDK for Java v2 release (or at minimum update the specific client/dependency versions that include fixes for the above CVEs). See the aws-sdk-java-v2 GitHub issue tracker for ongoing security issues and the module release pages for exact CVE lists per artifact. [1][2][3]

Sources:
[1] aws/aws-sdk-java-v2 GitHub — "Security vuln in aws sdk, via third-party (netty-nio-client) CVE-2025-24970". (github.com)
[2] Maven / software.amazon.awssdk:auth:2.26.0 (vulnerability list). (mvnrepository.com)
[3] Maven / software.amazon.awssdk:utils:2.26.0 (vulnerability list). (mvnrepository.com)
[4] Maven / software.amazon.awssdk:sts:2.26.0 (vulnerability list). (mvnrepository.com)

If you want, I can (a) fetch the latest patched v2 release that fixes these specific CVEs and give upgrade instructions, or (b) produce a per-artifact CVE list for your project's dependencies. Which would you prefer?

Citations:


AWS SDK 버전을 2.40.13 이상으로 업그레이드하세요.

현재 사용 중인 버전 2.26.0에는 보안 취약점이 있습니다. CVE-2025-24970(Netty TLS/SSLEngine 취약점)을 포함한 여러 CVE가 보고되었으며, 최신 안정 버전은 2.40.13(2025년 12월 20일 출시)입니다. 일관된 버전 관리는 좋으나, 보안 업그레이드가 필요합니다.

🤖 Prompt for AI Agents
In backend/build.gradle around lines 54 to 57, the AWS SDK dependencies are
pinned to 2.26.0 which contains known CVEs; update all software.amazon.awssdk
artifact versions to at least 2.40.13 (e.g., implementation
'software.amazon.awssdk:s3:2.40.13', implementation
'software.amazon.awssdk:auth:2.40.13', implementation
'software.amazon.awssdk:url-connection-client:2.40.13'), or better centralize
the SDK version in a single variable or BOM and reference it for consistency;
after updating, refresh Gradle dependencies and run the build/tests to verify no
breaking API changes.

Comment on lines +44 to 48
if (!now.isBefore(recruitmentStartDate) && now.isBefore(recruitmentEndDate)) {
return (recruitmentEndDate.getYear() == ALWAYS_RECRUIT_YEAR)
? ClubRecruitmentStatus.ALWAYS
: ClubRecruitmentStatus.OPEN;
}
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "RecruitmentStateCalculator.java" | head -20

Repository: Moadong/moadong

Length of output: 132


🏁 Script executed:

find . -type f -name "ClubRecruitmentInformation.java" | head -20

Repository: Moadong/moadong

Length of output: 134


🏁 Script executed:

cat -n ./backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java

Repository: Moadong/moadong

Length of output: 3796


🏁 Script executed:

cat -n ./backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java

Repository: Moadong/moadong

Length of output: 4749


🏁 Script executed:

# Verify if there are any other calls to getRecruitmentStart/End that might bypass the getter
rg "getRecruitmentStart|getRecruitmentEnd" --type java -B 2 -A 2

Repository: Moadong/moadong

Length of output: 8599


44번 줄의 불필요한 조건 검사를 제거하세요.

37번 줄의 if (now.isBefore(recruitmentStartDate)) 조건이 거짓일 때만 44번 줄에 도달하므로, 44번 줄의 !now.isBefore(recruitmentStartDate) 검사는 항상 참입니다. 이 부분을 제거하고 else if (now.isBefore(recruitmentEndDate)) 형태로 단순화할 수 있습니다.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java
around lines 44 to 48, the condition `!now.isBefore(recruitmentStartDate)` is
redundant because control only reaches this block when the prior `if
(now.isBefore(recruitmentStartDate))` is false; remove the unnecessary check and
simplify the branch to `else if (now.isBefore(recruitmentEndDate))` keeping the
same return logic (return ALWAYS when recruitmentEndDate.getYear() ==
ALWAYS_RECRUIT_YEAR, otherwise OPEN).

Comment on lines 75 to 81
@PostMapping("/{clubId}/feed/upload-url")
@Operation(summary = "피드 이미지 업로드 URL들 생성", description = "피드 이미지 업로드를 위한 Presigned URL을 여러 개 한 번에 생성합니다.")
public ResponseEntity<?> generateFeedUploadUrl(@PathVariable String clubId,
@RequestBody @Valid List<UploadUrlRequest> requests) {
List<PresignedUploadResponse> results = clubImageService.generateFeedUploadUrls(clubId, requests);
return Response.ok(results);
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -e java 'UploadUrlRequest' --type f

Repository: Moadong/moadong

Length of output: 120


🏁 Script executed:

cat backend/src/main/java/moadong/media/dto/UploadUrlRequest.java

Repository: Moadong/moadong

Length of output: 407


🏁 Script executed:

cat backend/src/main/java/moadong/media/controller/ClubImageController.java | head -30

Repository: Moadong/moadong

Length of output: 1343


🏁 Script executed:

fd -e "pom.xml" -o -e "build.gradle" | head -5

Repository: Moadong/moadong

Length of output: 175


🏁 Script executed:

find . -maxdepth 2 -name "pom.xml" -o -name "build.gradle"

Repository: Moadong/moadong

Length of output: 81


🏁 Script executed:

grep -i "jakarta.validation\|validation-api\|spring-boot" backend/build.gradle | head -20

Repository: Moadong/moadong

Length of output: 843


🏁 Script executed:

grep -i "org.springframework.boot.*version\|^plugins" backend/build.gradle -A 5 | head -15

Repository: Moadong/moadong

Length of output: 232


🏁 Script executed:

rg "MethodValidationPostProcessor|ValidationConfiguration|@Configuration.*Valid" backend/src --type java

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

rg "@Valid.*List|List.*@Valid" backend/src --type java -A 2 -B 2

Repository: Moadong/moadong

Length of output: 2470


리스트의 각 요소를 검증하려면 @Valid 위치를 변경해야 합니다.

@RequestBody @Valid List<UploadUrlRequest> requests는 리스트 자체만 검증하고 각 요소의 @NotBlank, @Pattern 같은 필드 유효성 검증을 하지 않습니다. 각 UploadUrlRequest 객체의 유효성을 검증하려면 @RequestBody List<@Valid UploadUrlRequest> requests로 변경하세요.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/media/controller/ClubImageController.java
around lines 75–81, the method parameter currently uses @RequestBody @Valid
List<UploadUrlRequest> which only validates the list itself; change the
parameter so validation applies to each element by using @RequestBody
List<@Valid UploadUrlRequest> requests (and keep the @Valid import); if
class-level validation is required ensure the controller is annotated with
@Validated.

…date-MOA-452

[refactor] 동아리 수정 API 명세 변경
@lepitaaar
Copy link
Contributor

백엔드 수고하셨습니다

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

♻️ Duplicate comments (4)
backend/src/main/java/moadong/club/entity/ClubApplicationForm.java (2)

30-30: 배열 내용을 불변으로 만들어야 합니다.

externalApplicationUrlAllowedstatic final로 선언되었지만, 배열의 참조만 불변이고 배열 내용은 여전히 수정 가능합니다. 이전 리뷰 코멘트에서 지적된 것처럼, List.of() 또는 Collections.unmodifiableList()를 사용하여 완전히 불변인 컬렉션으로 변경하는 것이 권장됩니다.


113-122: null 또는 빈 문자열 검증이 여전히 누락되어 있습니다.

이전 리뷰에서 지적된 것처럼, externalApplicationUrl이 null이거나 빈 문자열인 경우 Line 115에서 NullPointerException이 발생합니다. 이 검증 로직을 추가해야 합니다.

backend/src/main/java/moadong/club/entity/Club.java (1)

106-106: Null 안전성 검증이 필요합니다.

request.description()이 null인 경우 toEntity() 호출 시 NPE가 발생합니다. ClubInfoRequest.javadescription 필드에 대한 리뷰 코멘트를 참고하세요.

🔎 제안하는 수정 방안
-        this.clubDescription = request.description().toEntity();
+        if (request.description() != null) {
+            this.clubDescription = request.description().toEntity();
+        }

또는 ClubInfoRequestdescription 필드에 @NotNull 어노테이션을 추가하여 항상 값이 존재하도록 보장하세요.

backend/src/main/java/moadong/media/controller/ClubImageController.java (1)

79-86: 리스트의 각 요소를 검증하려면 @Valid 위치를 변경해야 합니다.

이전 리뷰에서 지적된 문제가 여전히 존재합니다. @RequestBody @Valid List<UploadUrlRequest> requests는 리스트 자체만 검증하고 각 요소의 @NotBlank, @Pattern 같은 필드 유효성 검증을 하지 않습니다.

🔎 수정 방법
-    public ResponseEntity<?> generateFeedUploadUrl(@PathVariable String clubId,
-                                                   @RequestBody @Valid List<UploadUrlRequest> requests,
-                                                   @CurrentUser CustomUserDetails user) {
+    public ResponseEntity<?> generateFeedUploadUrl(@PathVariable String clubId,
+                                                   @RequestBody List<@Valid UploadUrlRequest> requests,
+                                                   @CurrentUser CustomUserDetails user) {

또한 클래스 레벨에 @Validated 어노테이션이 필요할 수 있습니다:

+import org.springframework.validation.annotation.Validated;
+
+@Validated
 @RestController
 @RequestMapping("/api/club")
🧹 Nitpick comments (7)
backend/src/main/java/moadong/club/entity/ClubApplicationForm.java (2)

76-78: 선택적 리팩토링: 방어 코드가 중복될 수 있습니다.

formMode 필드는 이미 @NotNull@Builder.Default로 보호되고 있어 null이 될 수 없습니다. Optional.ofNullable() 체크는 기술적으로 중복이지만, 역직렬화나 리플렉션 같은 엣지 케이스로부터 보호할 수 있어 해롭지는 않습니다.

더 간단한 구현을 원한다면 단순히 return this.formMode;로 변경할 수 있습니다.

🔎 제안하는 간소화
 public ApplicationFormMode getFormMode() {
-    return Optional.ofNullable(this.formMode).orElse(ApplicationFormMode.INTERNAL);
+    return this.formMode;
 }

109-111: null 입력값 검증을 추가하는 것이 좋습니다.

updateFormMode 메서드가 null 파라미터를 받을 경우를 처리하지 않습니다. 서비스 레이어에서 검증할 수 있지만, 엔티티 자체에서도 방어적으로 null을 체크하는 것이 좋은 관행입니다.

🔎 제안하는 수정사항
 public void updateFormMode(ApplicationFormMode formMode) {
+    if (formMode == null) {
+        throw new IllegalArgumentException("formMode cannot be null");
+    }
     this.formMode = formMode;
 }
backend/src/main/java/moadong/club/payload/request/ClubRecruitmentInfoUpdateRequest.java (1)

9-9: externalApplicationUrl에 URL 형식 검증 추가를 고려해 주세요.

유효하지 않은 URL이 저장되는 것을 방지하기 위해 Bean Validation 어노테이션 추가를 권장합니다.

🔎 URL 검증 추가 제안
 package moadong.club.payload.request;
 
 import java.time.Instant;
+import jakarta.validation.constraints.Pattern;
 
 public record ClubRecruitmentInfoUpdateRequest(
     Instant recruitmentStart,
     Instant recruitmentEnd,
     String recruitmentTarget,
+    @Pattern(regexp = "^(https?://.*)?$", message = "유효한 URL 형식이어야 합니다")
     String externalApplicationUrl
 ) {
 
 }

또는 Hibernate Validator의 @URL 어노테이션을 사용할 수도 있습니다.

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

46-50: 변수 재사용으로 일관성을 개선하세요.

Line 47에서 club.getClubRecruitmentInformation().getLastModifiedDate()를 호출하는 대신, Line 36에서 이미 추출한 clubRecruitmentInformation 변수를 재사용하는 것이 좋습니다.

🔎 제안하는 리팩토링
 String lastModifiedDate = "";
-if (club.getClubRecruitmentInformation().getLastModifiedDate() != null) {
+if (clubRecruitmentInformation.getLastModifiedDate() != null) {
     DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm");
-    lastModifiedDate = club.getClubRecruitmentInformation().getLastModifiedDate().format(formatter);
+    lastModifiedDate = clubRecruitmentInformation.getLastModifiedDate().format(formatter);
 }
backend/src/main/java/moadong/club/repository/ClubSearchRepository.java (1)

50-50: 사용되지 않는 unwind 연산을 제거하세요.

라인 50의 Aggregation.unwind("club_tags", true)는 Club 엔티티에 존재하지 않는 필드를 참조합니다. 실제 tags 데이터는 recruitmentInformation.tags에만 저장되어 있으며, 프로젝션(라인 53-59)에서도 recruitmentInformation.tags를 사용하고 있습니다. 이 unwind 연산은 불필요하므로 제거하는 것이 좋습니다.

backend/src/main/java/moadong/media/service/CloudflareImageService.java (1)

179-209: 배치 URL 생성 로직의 오류 메시지를 개선하세요.

Line 188에서 remaining == 0일 때 TOO_MANY_FILES 오류를 반환하는데, 이는 사용자가 이미 최대 개수의 피드 이미지를 가지고 있는 경우입니다. 오류 메시지가 "요청한 파일이 너무 많음"보다는 "피드 이미지가 이미 최대 개수에 도달했음"을 명확히 표현하면 더 좋겠습니다.

🔎 개선 제안

현재는 ErrorCode.TOO_MANY_FILES를 재사용하고 있습니다. 더 명확한 에러 코드(예: ErrorCode.FEED_LIMIT_REACHED)를 추가하거나, 클라이언트가 응답에서 remaining == 0 상태를 구분할 수 있도록 메타데이터를 포함하는 것을 고려하세요.

backend/src/main/java/moadong/media/controller/ClubImageController.java (1)

88-88: 더 이상 사용하지 않는 API에 대한 명확한 문서화

Feed complete API가 deprecated되었다는 주석은 유용합니다. 향후 이 엔드포인트를 제거할 계획이라면, @Deprecated 어노테이션을 추가하거나 완전히 제거하는 것을 고려하세요.

📜 Review details

Configuration used: Organization 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 5c182ca and 283739d.

📒 Files selected for processing (21)
  • backend/src/main/java/moadong/club/entity/Club.java
  • backend/src/main/java/moadong/club/entity/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/entity/ClubAward.java
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
  • backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
  • backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java
  • backend/src/main/java/moadong/club/payload/dto/ClubAwardDto.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDescriptionDto.java
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
  • backend/src/main/java/moadong/club/payload/dto/ClubIdealCandidateDto.java
  • backend/src/main/java/moadong/club/payload/dto/FaqDto.java
  • backend/src/main/java/moadong/club/payload/request/ClubInfoRequest.java
  • backend/src/main/java/moadong/club/payload/request/ClubRecruitmentInfoUpdateRequest.java
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/media/controller/ClubImageController.java
  • backend/src/main/java/moadong/media/service/CloudflareImageService.java
  • backend/src/main/java/moadong/media/service/ClubImageService.java
  • backend/src/test/java/moadong/fixture/ClubFixture.java
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
  • backend/src/test/java/moadong/media/service/CloudflareImageServiceUpdateFeedsLimitTest.java
💤 Files with no reviewable changes (1)
  • backend/src/test/java/moadong/fixture/ClubFixture.java
✅ Files skipped from review due to trivial changes (1)
  • backend/src/main/java/moadong/club/entity/ClubDescription.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/src/test/java/moadong/fixture/ClubRequestFixture.java
  • backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java
  • backend/src/test/java/moadong/media/service/CloudflareImageServiceUpdateFeedsLimitTest.java
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.

Applied to files:

  • backend/src/main/java/moadong/club/entity/ClubAward.java
  • backend/src/main/java/moadong/club/entity/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/entity/Club.java
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 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/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/club/entity/Club.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 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/ClubApplicationForm.java
  • backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java
  • backend/src/main/java/moadong/club/service/ClubProfileService.java
  • backend/src/main/java/moadong/media/controller/ClubImageController.java
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 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/media/controller/ClubImageController.java
📚 Learning: 2025-05-01T08:32:09.398Z
Learnt from: Due-IT
Repo: Moadong/moadong PR: 329
File: backend/src/main/java/moadong/club/controller/ClubSearchController.java:0-0
Timestamp: 2025-05-01T08:32:09.398Z
Learning: ClubSearchController의 searchClubsByKeyword 메서드와 ClubSearchService의 searchClubsByKeyword 메서드 사이에 파라미터 순서 일치가 중요하다. 현재 컨트롤러의 매개변수 순서는 'keyword, recruitmentStatus, category, division'인 반면, 서비스 메서드의 순서는 'keyword, recruitmentStatus, division, category'이다. 컨트롤러에서 서비스 호출 시 'keyword, recruitmentStatus, division, category' 순서로 전달하면 category와 division 값이 바뀌어 검색 결과가 잘못될 수 있다.

Applied to files:

  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
🧬 Code graph analysis (6)
backend/src/main/java/moadong/club/entity/ClubAward.java (1)
backend/src/main/java/moadong/club/entity/ClubDescription.java (1)
  • Getter (10-27)
backend/src/main/java/moadong/club/payload/dto/ClubDescriptionDto.java (1)
frontend/src/types/club.ts (1)
  • ClubDescription (28-31)
backend/src/main/java/moadong/media/service/CloudflareImageService.java (2)
backend/src/main/java/moadong/club/entity/Club.java (1)
  • Slf4j (25-159)
backend/src/main/java/moadong/global/util/ObjectIdConverter.java (1)
  • ObjectIdConverter (7-17)
backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (4)
backend/src/main/java/moadong/user/entity/User.java (1)
  • Builder (21-99)
backend/src/main/java/moadong/club/entity/ClubAward.java (1)
  • Getter (10-19)
backend/src/main/java/moadong/club/entity/ClubDescription.java (1)
  • Getter (10-27)
backend/src/main/java/moadong/club/entity/Faq.java (1)
  • NoArgsConstructor (7-13)
backend/src/main/java/moadong/club/service/ClubProfileService.java (1)
backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)
  • RecruitmentStateCalculator (15-78)
backend/src/main/java/moadong/club/entity/Club.java (1)
frontend/src/types/club.ts (2)
  • Club (3-12)
  • ClubDescription (28-31)
⏰ 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 (27)
backend/src/main/java/moadong/club/entity/ClubApplicationForm.java (2)

66-68: LGTM!

formMode 필드가 적절하게 선언되었습니다. @NotNull@Builder.Default를 사용하여 기본값을 INTERNAL로 설정한 것은 하위 호환성을 위해 좋은 선택입니다.


70-71: LGTM!

externalApplicationUrl 필드 선언이 적절합니다. 빈 문자열 기본값은 외부 URL이 설정되지 않은 경우를 처리하기에 합리적입니다.

backend/src/main/java/moadong/club/entity/ClubIdealCandidate.java (1)

10-19: 구조가 깔끔합니다!

엔티티 구조가 명확하고 프로젝트의 다른 엔티티들(ClubAward, Faq)과 일관된 패턴을 따릅니다. Lombok 어노테이션을 적절히 사용하여 보일러플레이트 코드를 줄였습니다.

backend/src/main/java/moadong/club/entity/ClubAward.java (1)

10-19: LGTM!

ClubIdealCandidate와 일관된 구조로 구현되어 있으며, Lombok을 활용한 깔끔한 엔티티 정의입니다.

backend/src/main/java/moadong/club/service/ClubProfileService.java (2)

54-56: 메서드 시그니처 변경이 반영되었습니다.

ClubDetailedResult.of가 단일 Club 매개변수를 받도록 변경되어 API가 단순화되었습니다.


45-45: updateLastModifiedDate() 메서드가 올바르게 호출되고 있습니다.

ClubRecruitmentInformationupdateLastModifiedDate() 메서드가 존재하며, 상태 계산 후 적절한 위치에서 호출되어 모집 정보의 최종 수정 시점을 정확하게 기록합니다.

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

8-25: 검증과 매핑이 잘 구현되었습니다!

DTO 구조가 명확하고, @Size 검증 어노테이션이 적절히 적용되었습니다. from() 메서드에서 null 체크를 수행하여 안전성을 확보했습니다.

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

6-21: LGTM!

FAQ 데이터에 적합한 검증 규칙이 적용되었으며, ClubAwardDto와 일관된 패턴으로 구현되었습니다.

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

8-25: 검증 규칙이 적절합니다!

태그 요소별 크기 제한(10자)과 콘텐츠 크기 제한(700자)이 이상적인 지원자 설명에 적합하게 설정되었습니다. 다른 DTO들과 일관된 구조입니다.

backend/src/main/java/moadong/club/entity/Club.java (1)

49-50: 필드 초기화가 일관되게 적용되었습니다!

clubDescription 필드가 모든 생성자와 빌더에서 적절히 초기화되어, clubRecruitmentInformation과 동일한 패턴을 따릅니다.

Also applies to: 60-60, 70-70, 81-81, 93-93

backend/src/main/java/moadong/club/payload/dto/ClubDescriptionDto.java (3)

9-27: DTO 정의가 잘 구성되어 있습니다.

검증 제약 조건이 적절히 적용되어 있고, 중첩된 DTO에 대해 @Valid 어노테이션이 올바르게 사용되었습니다.


40-49: 엔티티 변환 로직이 올바르게 구현되어 있습니다.

모든 필드에 대해 적절한 null 처리가 되어 있으며, 중첩된 객체와 컬렉션 매핑이 일관성 있게 구현되었습니다.


28-38: 변경 불필요 - ClubIdealCandidateDto.from() 메서드는 이미 null을 안전하게 처리합니다.

ClubIdealCandidateDto.from() 메서드(라인 15)에서 if (candidate == null) return null; 로 null 입력을 명시적으로 처리하고 있습니다. 따라서 라인 34의 호출은 안전하며, awardsfaqs와 동일한 null-safe 패턴을 따르고 있습니다.

Likely an incorrect or invalid review comment.

backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (4)

38-44: 모집 기간 처리 로직이 잘 구현되어 있습니다.

기본값 "미정"을 설정하고 모집 기간이 있을 때만 포맷팅하는 방식이 적절합니다.


67-67: 설명 필드 매핑이 올바르게 구현되어 있습니다.

ClubDescriptionDto.from() 메서드를 적절히 사용하여 엔티티를 DTO로 변환하고 있습니다.


73-74: 새로운 필드들이 빌더에 올바르게 설정되어 있습니다.

recruitmentStart, recruitmentEnd, lastModifiedDate 필드가 계산된 값으로 적절히 설정되었습니다.

Also applies to: 83-83


35-35: 메서드 호출부를 확인한 결과, ClubDetailedResult.of() 메서드는 현재 of(Club club) 시그니처로 정의되어 있으며, 유일한 호출 위치인 ClubProfileService.java:54에서도 단일 파라미터로 일치하게 호출하고 있습니다. 파라미터 변경이 이미 모든 호출부에 올바르게 반영되어 있으므로 추가 조치가 필요하지 않습니다.

backend/src/main/java/moadong/club/repository/ClubSearchRepository.java (1)

22-24: 삭제된 searchRecommendClubs 메서드는 코드베이스 전체에서 참조되지 않습니다.

검색 결과 searchRecommendClubs 메서드에 대한 호출이 존재하지 않습니다. 컨트롤러, 서비스 및 테스트 클래스 어디에서도 이 메서드를 사용하지 않으므로 이 메서드의 삭제는 안전합니다.

backend/src/main/java/moadong/media/service/ClubImageService.java (1)

1-28: 인터페이스 변경사항이 적절합니다.

Presigned URL 기반 워크플로우로의 마이그레이션이 인터페이스 레벨에서 잘 반영되었습니다. 모든 메서드에 userId 파라미터가 추가되어 구현체에서 권한 검증을 수행할 수 있습니다.

backend/src/main/java/moadong/media/service/CloudflareImageService.java (5)

60-67: 초기화 로직이 올바릅니다.

@PostConstruct에서 viewEndpoint 설정을 검증하고 정규화하는 것은 fail-fast 원칙에 부합하며, 런타임 오류를 방지합니다.


112-121: 권한 검증 로직이 적절합니다.

getAuthorizedClub 메서드가 클럽 존재 여부와 소유권을 모두 검증하여, 이전 리뷰에서 지적된 권한 상승 취약점을 해결했습니다.


132-157: 강건한 오류 처리가 구현되어 있습니다.

deleteFile 메서드의 오류 처리는 다음을 포함합니다:

  • Null/empty 검증
  • S3Exception 및 일반 예외에 대한 적절한 로깅
  • 파일이 존재하지 않거나 삭제 실패 시에도 계속 진행

이는 클린업 작업의 안정성을 보장합니다.


296-329: Presigned URL 생성 로직이 안전합니다.

  • 랜덤 파일명 생성으로 충돌 방지
  • 적절한 만료 시간 (10분)
  • Content-Type 헤더 포함
  • 정규화된 viewEndpoint 사용

249-282: 파일 크기 검증이 업로드 후에 수행됩니다.

현재 구현은 Presigned URL 이후 서버에서 파일 크기를 검증합니다. 대용량 파일 업로드 후 삭제되면 대역폭이 낭비될 수 있습니다.

다만, AWS S3 SDK Java v2의 S3Presigner는 Presigned PUT URL에 대해 content-length-range 조건을 지원하지 않습니다. 이 조건은 Presigned POST 정책에서만 지원되는데, Java SDK가 네이티브 Presigned POST 생성 기능을 제공하지 않아 직접 구현이 필요합니다. 따라서 현재의 업로드 후 검증 방식이 Java 환경에서의 합리적인 절충안입니다. 더 나은 해결책이 필요하다면 Presigned POST 구현 또는 클라이언트 요청 단계에서 사전 검증을 고려하세요.

backend/src/main/java/moadong/media/controller/ClubImageController.java (3)

39-58: 권한 검증이 올바르게 구현되었습니다.

이전 리뷰에서 지적된 권한 상승 취약점이 해결되었습니다. 모든 삭제 및 업데이트 엔드포인트가 @CurrentUser를 통해 인증된 사용자 정보를 가져와 userId를 서비스 레이어에 전달하고 있습니다. 이를 통해 CloudflareImageService.getAuthorizedClub 메서드에서 소유권을 검증할 수 있습니다.


60-77: 로고 업로드 엔드포인트가 잘 구현되었습니다.

Presigned URL 생성 및 완료 엔드포인트가 다음을 포함합니다:

  • @CurrentUser를 통한 권한 검증
  • @Valid를 통한 요청 검증
  • 명확한 API 문서

90-107: 커버 이미지 업로드 엔드포인트가 잘 구현되었습니다.

로고 업로드와 동일한 패턴을 따르며, 일관성 있고 안전한 구현입니다.

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

🧹 Nitpick comments (1)
backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (1)

46-50: 중복 코드 제거 및 지역 변수 활용 권장.

두 가지 개선 사항이 있습니다:

  1. Line 47에서 club.getClubRecruitmentInformation()을 다시 호출하는 대신, Line 36에서 이미 추출한 clubRecruitmentInformation 지역 변수를 재사용해야 합니다.
  2. Line 41과 Line 48에서 동일한 DateTimeFormatter를 중복 생성하고 있습니다. 메서드 상단에서 한 번만 생성하여 재사용하는 것이 좋습니다.
🔎 리팩토링 제안
     public static ClubDetailedResult of(Club club) {
         ClubRecruitmentInformation clubRecruitmentInformation = club.getClubRecruitmentInformation();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm");
 
         String start = "미정";
         String end = "미정";
         if (clubRecruitmentInformation != null && clubRecruitmentInformation.hasRecruitmentPeriod()) {
-            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm");
             start = clubRecruitmentInformation.getRecruitmentStart().format(formatter);
             end = clubRecruitmentInformation.getRecruitmentEnd().format(formatter);
         }
 
         String lastModifiedDate = "";
-        if (club.getClubRecruitmentInformation().getLastModifiedDate() != null) {
-            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm");
-            lastModifiedDate = club.getClubRecruitmentInformation().getLastModifiedDate().format(formatter);
+        if (clubRecruitmentInformation != null && clubRecruitmentInformation.getLastModifiedDate() != null) {
+            lastModifiedDate = clubRecruitmentInformation.getLastModifiedDate().format(formatter);
         }
📜 Review details

Configuration used: Organization 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 283739d and 616dcbb.

📒 Files selected for processing (1)
  • backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-05-15T12:03:57.356Z
Learnt from: Zepelown
Repo: Moadong/moadong PR: 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/ClubDetailedResult.java
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 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/dto/ClubDetailedResult.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

Copy link
Member

@oesnuj oesnuj left a comment

Choose a reason for hiding this comment

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

LGTM
수고하셨습니다

@lepitaaar lepitaaar changed the title [release] BE [release] BE 1.1.0 Dec 26, 2025
@lepitaaar lepitaaar changed the title [release] BE 1.1.0 [release] BE v1.1.0 Dec 26, 2025
Copy link
Contributor

@lepitaaar lepitaaar 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 changed the title [release] BE v1.1.0 [release] BE Dec 26, 2025
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 (2)
backend/src/main/java/moadong/user/entity/User.java (1)

58-60: 다중 리프레시 토큰 수 제한 고려 필요.

다중 로그인을 지원하는 것은 좋지만, 사용자당 리프레시 토큰 수에 제한이 없으면 사용자가 로그아웃 없이 반복 로그인할 경우 무한히 증가할 수 있습니다. 이는 메모리 및 보안 문제를 야기할 수 있습니다.

🔎 토큰 수 제한을 적용하는 제안
private static final int MAX_REFRESH_TOKENS = 5; // 최대 동시 세션 수

public void addRefreshToken(RefreshToken refreshToken) {
    if (this.refreshTokens == null) {
        this.refreshTokens = new ArrayList<>();
    }
    // 제한 초과 시 가장 오래된 토큰 제거
    while (this.refreshTokens.size() >= MAX_REFRESH_TOKENS) {
        this.refreshTokens.remove(0);
    }
    this.refreshTokens.add(refreshToken);
}
backend/src/main/java/moadong/user/service/UserCommandService.java (1)

105-119: 스트림을 사용하여 코드 가독성 개선 가능.

현재 for 루프와 boolean 플래그 대신 스트림을 사용하면 더 간결하고 읽기 쉬운 코드가 됩니다.

🔎 스트림을 사용한 리팩토링 제안
-        boolean hasToken = false;
-        if (user.getRefreshTokens() != null) {
-            for (RefreshToken t : user.getRefreshTokens()) {
-                if (t.getToken().equals(refreshToken)) {
-                    hasToken = true;
-                    break;
-                }
-            }
-        }
-        if (!hasToken) {
+        boolean hasToken = user.getRefreshTokens() != null &&
+            user.getRefreshTokens().stream()
+                .anyMatch(t -> t.getToken().equals(refreshToken));
+        if (!hasToken) {
             throw new RestApiException(ErrorCode.TOKEN_INVALID);
         }
📜 Review details

Configuration used: Organization 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 616dcbb and bfa1fc3.

📒 Files selected for processing (3)
  • backend/src/main/java/moadong/user/entity/User.java
  • backend/src/main/java/moadong/user/repository/UserRepository.java
  • backend/src/main/java/moadong/user/service/UserCommandService.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
⏰ 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 (7)
backend/src/main/java/moadong/user/repository/UserRepository.java (1)

12-12: LGTM!

메서드명이 User 엔티티의 새로운 컬렉션 필드 refreshTokens와 일치하도록 올바르게 변경되었습니다. Spring Data MongoDB가 컬렉션 내 중첩 속성에 대한 쿼리를 올바르게 파생합니다.

backend/src/main/java/moadong/user/entity/User.java (1)

120-133: LGTM!

removeRefreshTokenremoveIf를 사용하여 깔끔하게 구현되었고, removeAllRefreshTokens는 null 처리를 올바르게 수행합니다.

backend/src/main/java/moadong/user/service/UserCommandService.java (5)

79-80: LGTM!

로그인 시 addRefreshToken을 사용하여 다중 토큰 모델을 올바르게 지원합니다.


88-92: LGTM!

로그아웃 시 특정 리프레시 토큰만 제거하여 다른 세션은 유지됩니다. 다중 디바이스 로그인 시나리오에 적합한 구현입니다.


121-124: 토큰 검증과 교체 사이의 잠재적 경쟁 조건.

토큰 존재 여부 확인(라인 105-116)과 교체(라인 123) 사이에 다른 요청(예: 로그아웃)이 토큰을 제거하면, replaceRefreshToken이 조용히 실패하여 새 토큰이 저장되지 않을 수 있습니다. @Transactional 어노테이션이 메서드에 없어 원자성이 보장되지 않습니다.

메서드에 @Transactional 추가 또는 replaceRefreshToken의 반환값을 확인하여 실패 시 처리하는 것을 고려해 주세요.


148-155: LGTM!

비밀번호 변경 시 모든 리프레시 토큰을 제거하고 새 토큰을 발급하는 것은 보안상 올바른 구현입니다. 다른 디바이스의 세션이 모두 무효화됩니다.


169-170: LGTM!

비밀번호 초기화 시 모든 리프레시 토큰을 제거하여 기존 세션을 무효화합니다. 사용자는 새 비밀번호로 다시 로그인해야 합니다.

@lepitaaar lepitaaar merged commit b534544 into main Dec 26, 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.

6 participants