Skip to content

Comments

[feature] 상시 모집 상태 추가 및 모집 정보 변경 시 반영되도록 변경#485

Merged
Due-IT merged 13 commits intodevelop/befrom
feature/#470-always-recruit-MOA-18
Jun 6, 2025
Merged

[feature] 상시 모집 상태 추가 및 모집 정보 변경 시 반영되도록 변경#485
Due-IT merged 13 commits intodevelop/befrom
feature/#470-always-recruit-MOA-18

Conversation

@Zepelown
Copy link
Member

@Zepelown Zepelown commented Jun 3, 2025

#️⃣연관된 이슈

#470

📝작업 내용

  • 상시 모집 상태 추가
  • 모집 종료 년도를 2999년으로 설정하면 상시 모집 상태로 변경되도록 구성
  • 기존 RecruitmentStateChecker 의 날짜 계산 로직 부분을 분리하여 RecruitmentStateCalculator 생성
    => 유틸 클래스로 만들어서 다른 서비스에서도 사용할 수 있도록 변경

모집 정보 수정 요청
image

모집 정보 수정 결과
image

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • 동아리 모집 상태 자동 계산 및 업데이트 기능이 추가되었습니다.
    • AWS S3 기반 이미지 업로드 및 관리 기능이 도입되었습니다.
    • 비밀번호 유효성 검사 기능이 강화되었습니다.
    • 파일 확장자 검증 및 이미지 자동 리사이징 기능이 추가되었습니다.
    • 클라우드플레어 연동 이미지 관리 기능이 명확히 안내됩니다.
    • 신규 에러 코드(지원하지 않는 파일 형식)가 추가되었습니다.
  • Refactor

    • 모집 상태 계산 로직이 별도 유틸리티로 분리되어 코드 유지보수가 용이해졌습니다.
    • 이미지 파일명 검증 로직이 확장되어 한글 및 특수문자 외에 다양한 부적절 문자도 감지합니다.
  • Tests

    • 비밀번호 유효성 검사 및 이미지 파일명 검증에 대한 단위 테스트가 추가 및 보완되었습니다.

@Zepelown Zepelown added ✨ Feature 기능 개발 📬 API 서버 API 통신 작업 💾 BE Backend labels Jun 3, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 3, 2025

"""

Walkthrough

이 변경 사항은 동아리 모집 상태를 계산하는 로직을 RecruitmentStateCalculator 유틸리티 클래스로 분리하고, 관련 서비스 및 체크 로직에서 해당 유틸리티를 사용하도록 리팩토링합니다. 또한, 2999년을 모집 종료일로 받으면 상시 모집(ALWAYS) 상태로 처리하는 기능이 추가되었습니다.

Changes

파일/경로 변경 요약
.../entity/Club.java import 순서 변경 및 생성자 파라미터 들여쓰기 조정
.../service/ClubProfileService.java 모집 상태 계산을 위해 RecruitmentStateCalculator 유틸리티 사용으로 변경
.../service/RecruitmentStateChecker.java 모집 상태 변경 로직을 RecruitmentStateCalculator로 위임
.../util/RecruitmentStateCalculator.java 모집 상태 계산 전용 RecruitmentStateCalculator 유틸리티 신규 추가

Sequence Diagram(s)

sequenceDiagram
    participant ClubProfileService
    participant RecruitmentStateCalculator
    participant Club

    ClubProfileService->>RecruitmentStateCalculator: calculate(club, startDate, endDate)
    RecruitmentStateCalculator->>Club: 모집 상태 업데이트
    ClubProfileService->>Club: 저장
Loading
sequenceDiagram
    participant RecruitmentStateChecker
    participant RecruitmentStateCalculator
    participant Club

    RecruitmentStateChecker->>RecruitmentStateCalculator: calculate(club, startDate, endDate)
    RecruitmentStateCalculator->>Club: 모집 상태 업데이트
    RecruitmentStateChecker->>Club: 저장
Loading

Assessment against linked issues

Objective Addressed Explanation
클럽 모집 정보 수정 시 2999년을 받으면 모집 상태를 ALWAYS로 변경한다 (MOA-18)

Assessment against linked issues: Out-of-scope changes

(해당 사항 없음)

Possibly related PRs

Suggested labels

🔨 Refactor

Suggested reviewers

  • Zepelown
  • lepitaaar
  • PororoAndFriends
    """
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • @coderabbitai modularize this function.
  • 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.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • 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

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.

@github-actions
Copy link

github-actions bot commented Jun 3, 2025

Test Results

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

Results for commit 589d0b7.

♻️ This comment has been updated with latest results.

@Zepelown
Copy link
Member Author

Zepelown commented Jun 3, 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 (1)
backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)

13-37: 메서드 문서화 권장

복잡한 비즈니스 로직을 포함하고 있으므로 Javadoc 주석을 추가하여 각 상태 변경 조건을 명확히 문서화하는 것을 권장합니다.

다음과 같은 Javadoc을 추가하는 것을 고려해보세요:

+    /**
+     * 클럽의 모집 기간을 기반으로 모집 상태를 계산하고 업데이트합니다.
+     * 
+     * @param club 모집 상태를 업데이트할 클럽
+     * @param recruitmentStartDate 모집 시작일 (null일 경우 CLOSED)
+     * @param recruitmentEndDate 모집 종료일 (null이거나 2999년일 경우 특별 처리)
+     */
     public static void calculate(Club club, ZonedDateTime recruitmentStartDate, ZonedDateTime recruitmentEndDate) {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6b63016 and 4f37e41.

📒 Files selected for processing (4)
  • backend/src/main/java/moadong/club/entity/Club.java (2 hunks)
  • backend/src/main/java/moadong/club/service/ClubProfileService.java (3 hunks)
  • backend/src/main/java/moadong/club/service/RecruitmentStateChecker.java (2 hunks)
  • backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
backend/src/main/java/moadong/club/service/ClubProfileService.java (2)
backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)
  • RecruitmentStateCalculator (10-38)
backend/src/main/java/moadong/global/util/ObjectIdConverter.java (1)
  • ObjectIdConverter (7-17)
backend/src/main/java/moadong/club/service/RecruitmentStateChecker.java (1)
backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)
  • RecruitmentStateCalculator (10-38)
🔇 Additional comments (12)
backend/src/main/java/moadong/club/entity/Club.java (2)

18-19: LGTM: 코드 스타일 개선

import 문 정렬이 코드 가독성을 향상시킵니다.


71-71: LGTM: 포맷팅 개선

생성자 파라미터 들여쓰기가 일관성 있게 개선되었습니다.

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

11-11: LGTM: 적절한 의존성 추가

새로운 RecruitmentStateCalculator 유틸리티 클래스를 import하여 모집 상태 계산 로직을 중앙화하는 것은 좋은 접근입니다.


47-51: LGTM: 모집 상태 계산 로직 통합

RecruitmentStateCalculator를 사용하여 모집 정보 업데이트 시 상태를 자동으로 계산하도록 개선되었습니다. 이는 일관성 있는 상태 관리를 보장합니다.


27-30: LGTM: 코드 포맷팅 개선

메서드 파라미터 정렬 및 예외 처리 포맷팅이 일관성 있게 개선되었습니다.

Also applies to: 44-44, 58-58, 61-61, 68-68

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

8-8: LGTM: 유틸리티 클래스 도입

RecruitmentStateCalculator 유틸리티 클래스를 도입하여 코드 중복을 제거하고 유지보수성을 향상시켰습니다.


12-13: LGTM: Import 정리

시간 관련 import가 깔끔하게 정리되었습니다.


31-31: LGTM: 로직 중앙화

복잡한 모집 상태 계산 로직을 RecruitmentStateCalculator로 위임하여 코드 중복을 제거하고 일관성을 보장합니다.

backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (4)

11-11: LGTM: 상시 모집 상수 정의

2999년을 상시 모집 연도로 정의한 것은 명확하고 이해하기 쉽습니다.


14-21: LGTM: 예외 상황 처리

null 값 처리와 상시 모집 상태 처리가 적절히 구현되었습니다.


22-22: LGTM: 타임존 설정

Asia/Seoul 타임존을 명시적으로 지정하여 정확한 시간 비교를 보장합니다.


32-36: LGTM: 모집 상태 로직

OPEN 및 CLOSED 상태 결정 로직이 올바르게 구현되었습니다.

Comment on lines +25 to +31
if (now.isBefore(recruitmentStartDate)) {
long between = ChronoUnit.DAYS.between(recruitmentStartDate, now);
if (between <= 14) {
club.updateRecruitmentStatus(ClubRecruitmentStatus.UPCOMING);
} else {
club.updateRecruitmentStatus(ClubRecruitmentStatus.CLOSED);
}
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

UPCOMING 상태 계산 로직 오류 수정 필요

현재 로직에 오류가 있습니다. now.isBefore(recruitmentStartDate) 상황에서 ChronoUnit.DAYS.between(recruitmentStartDate, now)를 계산하면 음수가 반환되는데, between <= 14 조건이 항상 true가 됩니다.

다음과 같이 수정해야 합니다:

-            long between = ChronoUnit.DAYS.between(recruitmentStartDate, now);
+            long between = ChronoUnit.DAYS.between(now, recruitmentStartDate);

이렇게 하면 모집 시작까지 남은 일수를 올바르게 계산하여, 14일 이내일 때만 UPCOMING 상태로 설정됩니다.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java
around lines 25 to 31, the calculation of days between recruitmentStartDate and
now is reversed causing negative values and incorrect UPCOMING status. Fix this
by swapping the arguments in ChronoUnit.DAYS.between to calculate days from now
to recruitmentStartDate, ensuring the difference is positive and the condition
correctly checks if the recruitment start is within 14 days.

[feature] cloudflare 이미지 저장 기능 구현
Copy link
Collaborator

@Due-IT Due-IT left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 프론트에게 변경내용을 잘 전달해야겠네요

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.

고생하셨습니다!

Zepelown and others added 5 commits June 4, 2025 23:59
fix: 잘못된 비밀번호 검증 로직과 잘못된 비밀번호 테스트 수정
이미지 저장소 cloudflare로 고정
- recruitmentStatus -> clubRecruitmentStatus
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: 5

🔭 Outside diff range comments (1)
backend/src/main/java/moadong/club/repository/ClubSearchRepository.java (1)

72-73: ⚠️ Potential issue

필터링 조건에서도 동일한 필드명을 사용해야 합니다.

projection에서 clubRecruitmentStatus로 변경했지만, 여기서는 여전히 이전 필드명 recruitmentStatus를 사용하고 있습니다. 이로 인해 모집 상태 필터링이 제대로 작동하지 않을 수 있습니다.

다음과 같이 수정하여 일관성을 맞춰주세요:

-            criteriaList.add(
-                Criteria.where("recruitmentInformation.recruitmentStatus").is(recruitmentStatus));
+            criteriaList.add(
+                Criteria.where("recruitmentInformation.clubRecruitmentStatus").is(recruitmentStatus));
🧹 Nitpick comments (1)
backend/src/main/java/moadong/media/controller/ClubImageController.java (1)

29-33: 필드 선언을 생성자 앞으로 이동하세요.

Java 컨벤션에 따르면 필드는 생성자보다 먼저 선언되어야 합니다.

+private final ClubImageService clubImageService;
+
 public ClubImageController(@Qualifier("cloudflare") ClubImageService clubImageService) {
     this.clubImageService = clubImageService;
 }
-
-private final ClubImageService clubImageService;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4f37e41 and 589d0b7.

📒 Files selected for processing (14)
  • backend/build.gradle (1 hunks)
  • backend/src/main/java/moadong/club/repository/ClubSearchRepository.java (1 hunks)
  • backend/src/main/java/moadong/global/exception/ErrorCode.java (1 hunks)
  • backend/src/main/java/moadong/global/validator/PasswordValidator.java (1 hunks)
  • backend/src/main/java/moadong/media/controller/ClubImageController.java (2 hunks)
  • backend/src/main/java/moadong/media/service/CloudflareImageService.java (1 hunks)
  • backend/src/main/java/moadong/media/service/GcsClubImageService.java (2 hunks)
  • backend/src/main/java/moadong/media/service/GoogleDriveClubImageService.java (3 hunks)
  • backend/src/main/java/moadong/media/util/ClubImageUtil.java (1 hunks)
  • backend/src/main/java/moadong/media/util/S3Config.java (1 hunks)
  • backend/src/test/java/moadong/media/service/GoogleDriveClubImageServiceFeedTest.java (2 hunks)
  • backend/src/test/java/moadong/media/service/GoogleDriveClubImageServiceLogoTest.java (3 hunks)
  • backend/src/test/java/moadong/unit/user/PasswordValidatorTest.java (1 hunks)
  • backend/src/test/java/moadong/unit/user/UserRegisterTest.java (0 hunks)
💤 Files with no reviewable changes (1)
  • backend/src/test/java/moadong/unit/user/UserRegisterTest.java
✅ Files skipped from review due to trivial changes (3)
  • backend/src/test/java/moadong/media/service/GoogleDriveClubImageServiceFeedTest.java
  • backend/src/main/java/moadong/global/exception/ErrorCode.java
  • backend/src/main/java/moadong/media/util/S3Config.java
🧰 Additional context used
🧬 Code Graph Analysis (2)
backend/src/main/java/moadong/media/service/GcsClubImageService.java (1)
backend/src/main/java/moadong/media/util/ClubImageUtil.java (1)
  • ClubImageUtil (13-62)
backend/src/test/java/moadong/media/service/GoogleDriveClubImageServiceLogoTest.java (1)
backend/src/main/java/moadong/media/util/ClubImageUtil.java (1)
  • ClubImageUtil (13-62)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (18)
backend/src/main/java/moadong/global/validator/PasswordValidator.java (3)

10-11: 정규식 패턴이 올바르게 개선되었습니다.

정규식을 상수로 분리한 것은 유지보수성을 높이는 좋은 변경입니다. (?!.*\\s) 추가로 공백 문자를 명시적으로 금지하고, \d 대신 [0-9]를 사용한 것도 명확성을 높입니다.


14-14: 매개변수 이름 변경이 일관성을 높입니다.

constraintValidatorContext에서 context로 변경한 것이 코드 가독성을 개선합니다.


15-17: null/empty 체크 로직이 명확해졌습니다.

null이나 빈 문자열에 대해 명시적으로 false를 반환하도록 변경한 것이 의도를 더욱 분명하게 합니다.

backend/src/test/java/moadong/unit/user/PasswordValidatorTest.java (4)

11-13: 테스트 클래스 구조가 잘 설계되었습니다.

@UnitTest 어노테이션 사용과 PasswordValidator 인스턴스 생성이 적절합니다.


15-19: 유효한 비밀번호 테스트가 적절합니다.

UserFixture를 사용하여 유효한 비밀번호를 테스트하는 것이 일관성 있는 접근입니다.


21-39: 포괄적인 무효 비밀번호 테스트 케이스들입니다.

매개변수화된 테스트로 다양한 무효 케이스들을 체계적으로 검증하고 있습니다:

  • 길이 제한 (너무 짧음/김)
  • 필수 문자 유형 누락 (숫자, 영문자)
  • 허용되지 않은 특수문자 (*, (), ~ 등)
  • 공백 포함
  • 다양한 조합 실패 케이스

테스트 커버리지가 정규식의 모든 조건을 잘 검증하고 있습니다.


29-29:

Details

✅ Verification successful

허용되지 않은 특수문자 테스트 케이스를 확인해주세요.

정규식에서 허용하는 특수문자는 [!@#$%^]인데, 테스트 케이스에서 *가 허용되지 않는다고 명시되어 있습니다. 이는 정확합니다.

다음 스크립트로 정규식과 테스트 케이스의 일치성을 확인해보겠습니다:


🏁 Script executed:

#!/bin/bash
# 설명: PasswordValidator의 정규식과 테스트 케이스들이 일치하는지 확인

# 정규식에서 허용하는 특수문자 확인
echo "정규식에서 허용하는 특수문자: [!@#\$%^]"

# 테스트 케이스에서 허용되지 않는다고 명시한 특수문자들 확인
echo "테스트에서 허용되지 않는다고 명시한 특수문자들:"
rg -A 1 "허용되지 않은 특수문자" backend/src/test/java/moadong/unit/user/PasswordValidatorTest.java

Length of output: 433


허용되지 않는 특수문자(*) 테스트 일치 확인
테스트 케이스의 *는 정규식([!@#$%^])에 포함되지 않아 올바르게 검증되고 있습니다.

backend/src/main/java/moadong/media/service/GcsClubImageService.java (2)

3-3: 파일명 검증 로직 개선이 잘 반영되었습니다.

기존의 containsKorean에서 containsInvalidChars로 변경하여 퍼센트 인코딩, 한글, 공백 등을 포함한 더 포괄적인 파일명 검증이 가능해졌습니다.


143-145: 개선된 파일명 검증 로직이 적절히 적용되었습니다.

containsInvalidChars 메서드를 사용하여 파일명에 유효하지 않은 문자가 포함된 경우 랜덤 문자열로 교체하는 로직이 올바르게 구현되었습니다.

backend/build.gradle (1)

57-63:

Details

❓ Verification inconclusive

새로운 의존성 추가를 확인해 주세요.

AWS S3 통합과 이미지 처리를 위한 의존성들이 추가되었습니다. 다음 사항을 확인해 주세요:

  • AWS SDK BOM 2.25.8 버전이 최신 안정 버전인지 확인
  • 새로 추가되는 CloudflareImageService와의 연관성 검토
  • Thumbnailator 0.4.14 버전의 보안 취약점 여부 확인

AWS SDK와 Thumbnailator의 최신 버전과 보안 이슈를 확인하기 위해 다음을 검색해 주세요:


🌐 Web query:

AWS SDK for Java BOM 2.25.8 version latest security advisories

💡 Result:

Based on the search results, I'll provide an overview of the AWS SDK for Java BOM version 2.25.8 and relevant security information.

The AWS SDK for Java is currently on version 2.x, which is a major rewrite of the 1.x codebase built on top of Java 8+ with several new features including support for non-blocking I/O and the ability to use different HTTP implementations at runtime[9]. The latest version appears to be around 2.31.32, released on April 29, 2025[6].

Regarding security advisories specifically for version 2.25.8, there is no direct mention of this exact version in the search results. However, the most recent security advisory mentioned is from July 15, 2022, which identified a "Partial Path Traversal in com.amazonaws:aws-java-sdk-s3" vulnerability (GHSA-c28r-hw5m-5gv3) with a "High" severity rating[2].

The AWS SDK for Java v2 has had several recent releases with various bugfixes and features. The most recent releases include:

  1. Version 2.31.32 (April 29, 2025) which included:

    • Fixed timestamp unmarshalling off-by-one errors
    • Added LegacyMd5Plugin to perform MD5 checksums for operations that require checksum
    • Updated endpoint and partition metadata[6]
  2. Version 2.31.31 (April 28, 2025) which included:

    • Addition of native h2 support for the bedrock runtime API
    • Updates to various AWS services including Certificate Manager, CloudFront, and DynamoDB[6]

There was also a recent issue with S3Client.deleteObjects not working in version 2.30.0, related to changes in checksum handling. This issue particularly affected Minio implementations but has since been resolved[1].

To use the latest AWS SDK for Java in your project, you would need to declare it as a dependency in your project's pom.xml file. The recommended approach is to use the BOM (Bill of Materials) to manage dependencies:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.31.32</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Then you can add specific modules without specifying their version:

<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3</artifactId>
    </dependency>
</dependencies>

For optimal security, it would be advisable to upgrade to the latest AWS SDK for Java version rather than staying on 2.25.8, as the newer versions contain security fixes and improvements[5][6].

Citations:


🌐 Web query:

Thumbnailator 0.4.14 library security vulnerabilities

💡 Result:

As of June 6, 2025, there are no known security vulnerabilities associated with Thumbnailator version 0.4.14. The Maven Repository lists a dependency vulnerability, CVE-2020-15250, but this pertains to a different component and does not affect Thumbnailator. (mvnrepository.com)

Additionally, the Sonatype OSS Index reports no vulnerabilities detected in Thumbnailator. (ossindex.sonatype.org)

It's important to note that Thumbnailator 0.4.14 was released on March 10, 2021. The latest version available is 0.4.20, released on June 27, 2023. (repo1.maven.org)

While no vulnerabilities have been identified in version 0.4.14, it's advisable to consider upgrading to the latest version to benefit from any improvements and security patches.

Citations:


AWS SDK 및 Thumbnailator 버전 검토 필요

다음 사항을 확인하고 필요 시 버전 업그레이드를 검토해 주세요:

  • backend/build.gradle (57–63):
    – AWS SDK BOM 2.25.8 → 최신 안정 버전은 2.31.32(2025-04-29)입니다. 보안 패치와 버그 픽스 적용을 위해 업그레이드를 권장합니다.
  • Thumbnailator 0.4.14:
    – 알려진 보안 취약점은 없으나 최신 버전은 0.4.20(2023-06-27)이므로 업데이트 고려가 필요합니다.
  • CloudflareImageService와의 연관성:
    – 실제 구현에서 해당 의존성을 사용하는지 코드베이스에서 직접 확인해 주세요.
backend/src/main/java/moadong/media/service/GoogleDriveClubImageService.java (2)

3-3: 파일명 검증 로직 개선이 일관되게 적용되었습니다.

다른 이미지 서비스와 동일하게 containsInvalidChars 메서드를 사용하도록 변경되어 일관성이 유지되었습니다.


25-25: 서비스 qualifier 추가가 적절합니다.

여러 ClubImageService 구현체를 구분하기 위한 "googleDrive" qualifier 추가가 적절합니다. 이는 새로 추가되는 CloudflareImageService와의 구분을 위한 것으로 보입니다.

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

4-4: 테스트에 필요한 import가 적절히 추가되었습니다.

새로운 테스트 메서드를 위한 assertion 메서드들과 유틸리티 클래스 import가 올바르게 추가되었습니다.

Also applies to: 7-7, 23-24


132-150: 포괄적인 파일명 검증 테스트가 잘 구현되었습니다.

containsInvalidChars 메서드에 대한 다양한 테스트 케이스가 포함되어 있어 좋습니다:

  • 정상 파일명
  • 한글 포함
  • 퍼센트 인코딩 포함
  • 공백 포함
  • 복합 조건

테스트 커버리지가 충분하고 엣지 케이스들이 잘 다뤄지고 있습니다.

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

36-36: Swagger 설명 업데이트가 적절합니다.

모든 엔드포인트의 설명이 일관되게 "cloudflare"를 명시하도록 업데이트되었습니다.

Also applies to: 44-44, 51-51, 57-57

backend/src/main/java/moadong/media/util/ClubImageUtil.java (2)

17-20: 파일명 검증 로직이 개선되었습니다.

메서드명이 더 명확해졌고, 퍼센트 인코딩, 한글, 공백 문자를 모두 검증하도록 확장되었습니다.


15-15: 이미지 확장자 검증 로직이 잘 구현되었습니다.

허용된 확장자 목록을 Set으로 관리하고, null 체크와 확장자 추출 로직이 적절합니다.

Also applies to: 22-28

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

1-173: PR 목적과 제공된 파일이 일치하지 않습니다.

PR 설명에는 "상시 모집 상태 추가" 및 "RecruitmentStateCalculator로 리팩토링"이 언급되어 있지만, 제공된 파일들은 모두 이미지 처리 관련 변경사항입니다. 올바른 파일을 검토하고 있는지 확인해 주세요.

Likely an incorrect or invalid review comment.

Aggregation.project("name", "state", "category", "division")
.and("recruitmentInformation.introduction").as("introduction")
.and("recruitmentInformation.recruitmentStatus").as("recruitmentStatus")
.and("recruitmentInformation.clubRecruitmentStatus").as("recruitmentStatus")
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

필드명 변경에 따른 일관성 확인이 필요합니다.

projection에서 recruitmentInformation.clubRecruitmentStatus로 변경되었지만, 72-73번째 줄의 getMatchedCriteria 메서드에서는 여전히 recruitmentInformation.recruitmentStatus를 사용하고 있습니다. 이로 인해 필터링과 projection 간에 불일치가 발생할 수 있습니다.

다음 스크립트를 실행하여 전체 코드베이스에서 필드명 사용 현황을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Description: Check usage of recruitment status field names across the codebase

echo "=== Searching for old field name 'recruitmentStatus' ==="
rg -A 3 "recruitmentInformation\.recruitmentStatus"

echo "=== Searching for new field name 'clubRecruitmentStatus' ==="
rg -A 3 "recruitmentInformation\.clubRecruitmentStatus"

echo "=== Checking RecruitmentStateCalculator usage ==="
rg -A 5 "RecruitmentStateCalculator"

Length of output: 4971


getMatchedCriteria 필드명 일관성 수정 필요

getMatchedCriteria 메서드(72~73행)에서 여전히 사용 중인 "recruitmentInformation.recruitmentStatus"를 프로젝션에서 사용 중인 새 필드명 "recruitmentInformation.clubRecruitmentStatus"로 변경해야 합니다. 그렇지 않으면 필터링이 의도한 컬럼에 적용되지 않습니다.

대상 위치:

  • 파일: backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
  • 라인: 약 72행 (getMatchedCriteria 내부)

제안하는 변경(diff):

- criteriaList.add(Criteria.where("recruitmentInformation.recruitmentStatus").is(recruitmentStatus));
+ criteriaList.add(Criteria.where("recruitmentInformation.clubRecruitmentStatus").is(recruitmentStatus));
📝 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
.and("recruitmentInformation.clubRecruitmentStatus").as("recruitmentStatus")
// ── getMatchedCriteria 메서드 내부 ──
private List<Criteria> getMatchedCriteria(RecruitmentStatus recruitmentStatus) {
List<Criteria> criteriaList = new ArrayList<>();
// …기존 다른 필터 조건…
- criteriaList.add(Criteria.where("recruitmentInformation.recruitmentStatus")
- .is(recruitmentStatus));
+ criteriaList.add(Criteria.where("recruitmentInformation.clubRecruitmentStatus")
+ .is(recruitmentStatus));
return criteriaList;
}
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/club/repository/ClubSearchRepository.java
around line 72, update the field name used in the getMatchedCriteria method from
"recruitmentInformation.recruitmentStatus" to
"recruitmentInformation.clubRecruitmentStatus" to maintain consistency with the
projection field name and ensure correct filtering behavior.

Comment on lines +138 to 140
if (containsInvalidChars(fileName)) {
fileName = RandomStringUtil.generateRandomString(10);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

파일명 검증 로직이 올바르게 개선되었습니다.

containsInvalidChars 메서드를 사용하여 더 포괄적인 파일명 검증을 수행하도록 변경되었습니다. 다만 파일 확장자가 누락되는 문제가 있습니다.

현재 로직에서는 유효하지 않은 문자가 포함된 경우 확장자 없이 랜덤 문자열만 생성합니다:

-        if (containsInvalidChars(fileName)) {
-            fileName = RandomStringUtil.generateRandomString(10);
-        }
+        if (containsInvalidChars(fileName)) {
+            String extension = "";
+            if (fileName.contains(".")) {
+                extension = fileName.substring(fileName.lastIndexOf("."));
+            }
+            fileName = RandomStringUtil.generateRandomString(10) + extension;
+        }
📝 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
if (containsInvalidChars(fileName)) {
fileName = RandomStringUtil.generateRandomString(10);
}
if (containsInvalidChars(fileName)) {
String extension = "";
if (fileName.contains(".")) {
extension = fileName.substring(fileName.lastIndexOf("."));
}
fileName = RandomStringUtil.generateRandomString(10) + extension;
}
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/media/service/GoogleDriveClubImageService.java
around lines 138 to 140, the file name validation correctly checks for invalid
characters but replaces the entire file name with a random string, causing the
file extension to be lost. Modify the logic to preserve the original file
extension by extracting it from the original file name and appending it to the
generated random string when invalid characters are found.

Comment on lines +30 to 60
public static MultipartFile resizeImage(MultipartFile file, long maxSizeBytes) throws IOException {
double quality = 0.9;
int maxDim = 2000;
byte[] result;

while (true) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Thumbnails.of(file.getInputStream())
.size(maxDim, maxDim)
.outputQuality(quality)
.outputFormat("jpg") // 용량 줄이기 좋음
.toOutputStream(baos);

result = baos.toByteArray();

if (result.length <= maxSizeBytes || (quality <= 0.3 && maxDim <= 800)) {
break;
}
quality -= 0.1;
maxDim -= 200;
file = new MockMultipartFile(file.getName(), file.getOriginalFilename(),
file.getContentType(), new ByteArrayInputStream(file.getBytes()));
}

return new MockMultipartFile(
file.getName(),
file.getOriginalFilename(),
"image/jpeg",
new ByteArrayInputStream(result)
);
}
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

이미지 리사이징 로직에 버그가 있습니다.

51번 줄에서 file.getBytes()를 사용하면 원본 파일의 바이트를 계속 읽어오므로, 리사이징된 이미지가 아닌 원본 이미지를 반복적으로 처리하게 됩니다.

-            file = new MockMultipartFile(file.getName(), file.getOriginalFilename(),
-                    file.getContentType(), new ByteArrayInputStream(file.getBytes()));
+            file = new MockMultipartFile(file.getName(), file.getOriginalFilename(),
+                    "image/jpeg", new ByteArrayInputStream(result));

추가로, 무한 루프를 방지하기 위해 최대 반복 횟수를 설정하는 것을 고려해보세요.

📝 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
public static MultipartFile resizeImage(MultipartFile file, long maxSizeBytes) throws IOException {
double quality = 0.9;
int maxDim = 2000;
byte[] result;
while (true) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Thumbnails.of(file.getInputStream())
.size(maxDim, maxDim)
.outputQuality(quality)
.outputFormat("jpg") // 용량 줄이기 좋음
.toOutputStream(baos);
result = baos.toByteArray();
if (result.length <= maxSizeBytes || (quality <= 0.3 && maxDim <= 800)) {
break;
}
quality -= 0.1;
maxDim -= 200;
file = new MockMultipartFile(file.getName(), file.getOriginalFilename(),
file.getContentType(), new ByteArrayInputStream(file.getBytes()));
}
return new MockMultipartFile(
file.getName(),
file.getOriginalFilename(),
"image/jpeg",
new ByteArrayInputStream(result)
);
}
public static MultipartFile resizeImage(MultipartFile file, long maxSizeBytes) throws IOException {
double quality = 0.9;
int maxDim = 2000;
byte[] result;
while (true) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Thumbnails.of(file.getInputStream())
.size(maxDim, maxDim)
.outputQuality(quality)
.outputFormat("jpg") // 용량 줄이기 좋음
.toOutputStream(baos);
result = baos.toByteArray();
if (result.length <= maxSizeBytes || (quality <= 0.3 && maxDim <= 800)) {
break;
}
quality -= 0.1;
maxDim -= 200;
- file = new MockMultipartFile(file.getName(), file.getOriginalFilename(),
- file.getContentType(), new ByteArrayInputStream(file.getBytes()));
+ file = new MockMultipartFile(file.getName(), file.getOriginalFilename(),
+ "image/jpeg", new ByteArrayInputStream(result));
}
return new MockMultipartFile(
file.getName(),
file.getOriginalFilename(),
"image/jpeg",
new ByteArrayInputStream(result)
);
}
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/media/util/ClubImageUtil.java between lines 30
and 60, the resizeImage method incorrectly uses file.getBytes() inside the loop,
which always reads the original file bytes instead of the resized image bytes,
causing repeated processing of the original image. To fix this, update the file
variable with the resized image bytes stored in the result array instead of the
original file bytes. Additionally, add a maximum iteration count to the while
loop to prevent potential infinite looping when the image cannot be resized
below maxSizeBytes.

Comment on lines +134 to +136
if (containsInvalidChars(fileName)) {
fileName = RandomStringUtil.generateRandomString(10);
}
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

파일 확장자를 유지해야 합니다.

파일명에 유효하지 않은 문자가 있을 때 전체 파일명을 랜덤 문자열로 교체하면 확장자가 손실됩니다.

 if (containsInvalidChars(fileName)) {
-    fileName = RandomStringUtil.generateRandomString(10);
+    String extension = fileName.substring(fileName.lastIndexOf("."));
+    fileName = RandomStringUtil.generateRandomString(10) + extension;
 }
📝 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
if (containsInvalidChars(fileName)) {
fileName = RandomStringUtil.generateRandomString(10);
}
if (containsInvalidChars(fileName)) {
String extension = fileName.substring(fileName.lastIndexOf("."));
fileName = RandomStringUtil.generateRandomString(10) + extension;
}
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/media/service/CloudflareImageService.java
around lines 134 to 136, when the fileName contains invalid characters, the
current code replaces the entire fileName with a random string, causing the file
extension to be lost. Modify the code to preserve the original file extension by
extracting it before generating the random string and then appending it to the
new random fileName.

Comment on lines +111 to +121
public void deleteFile(Club club, String filePath) {
// https://pub-8655aea549d544239ad12d0385aa98aa.r2.dev/{key} -> {key}
String key = filePath.substring(viewEndpoint.length()+1);

DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();

s3Client.deleteObject(deleteRequest);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

URL 파싱 로직을 개선하세요.

현재 substring을 사용한 방식은 URL 형식이 변경되면 오류가 발생할 수 있습니다. URL 파싱을 더 안전하게 처리하는 것이 좋습니다.

 public void deleteFile(Club club, String filePath) {
-    // https://pub-8655aea549d544239ad12d0385aa98aa.r2.dev/{key} -> {key}
-    String key = filePath.substring(viewEndpoint.length()+1);
+    if (!filePath.startsWith(viewEndpoint)) {
+        throw new RestApiException(ErrorCode.INVALID_FILE_PATH);
+    }
+    String key = filePath.substring(viewEndpoint.length())
+            .replaceFirst("^/", ""); // 선행 슬래시 제거
📝 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
public void deleteFile(Club club, String filePath) {
// https://pub-8655aea549d544239ad12d0385aa98aa.r2.dev/{key} -> {key}
String key = filePath.substring(viewEndpoint.length()+1);
DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();
s3Client.deleteObject(deleteRequest);
}
public void deleteFile(Club club, String filePath) {
if (!filePath.startsWith(viewEndpoint)) {
throw new RestApiException(ErrorCode.INVALID_FILE_PATH);
}
String key = filePath.substring(viewEndpoint.length())
.replaceFirst("^/", ""); // 선행 슬래시 제거
DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();
s3Client.deleteObject(deleteRequest);
}
🤖 Prompt for AI Agents
In backend/src/main/java/moadong/media/service/CloudflareImageService.java
around lines 111 to 121, the current method uses substring to extract the key
from the filePath URL, which is fragile if the URL format changes. Replace this
with a more robust URL parsing approach, such as using Java's URI or URL classes
to parse the filePath and safely extract the path component after the
viewEndpoint, ensuring the key is correctly obtained regardless of URL format
variations.

@Due-IT Due-IT merged commit 23a5275 into develop/be Jun 6, 2025
3 checks passed
@Zepelown Zepelown deleted the feature/#470-always-recruit-MOA-18 branch June 10, 2025 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신 작업 💾 BE Backend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants