Skip to content

[FEAT] 모임 생성 쿨다운과 모임 이미지 V2 구현#135

Merged
LimdaeIl merged 4 commits intomainfrom
feat/image-upload-v2-cooldown
Dec 18, 2025
Merged

[FEAT] 모임 생성 쿨다운과 모임 이미지 V2 구현#135
LimdaeIl merged 4 commits intomainfrom
feat/image-upload-v2-cooldown

Conversation

@LimdaeIl
Copy link
Collaborator

@LimdaeIl LimdaeIl commented Dec 18, 2025

📝 Pull Request

📌 PR 종류

해당하는 항목에 체크해주세요.

  • 기능 추가 (Feature)
  • 버그 수정 (Fix)
  • 문서 수정 (Docs)
  • 코드 리팩터링 (Refactor)
  • 테스트 추가 (Test)
  • 기타 변경 (Chore)

✨ 변경 내용

Redis에 임시 저장소를 사용하는 새로운 이미지 사전 업로드 흐름을 도입하여 클라이언트가 그룹 생성 전에 이미지를 업로드할 수 있도록 합니다.
악용 방지를 위해 Redis를 사용한 그룹 생성 쿨다운 기능을 추가합니다. 이 기능에는 실패 시 쿨다운 잠금을 해제하는 트랜잭션 롤백 처리가 포함됩니다.
사전 업로드된 이미지와 이미지 키를 활용하도록 그룹 생성 프로세스를 재구성하여 보안과 효율성을 향상시킵니다.

🔍 관련 이슈

🧪 테스트

변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.

  • 유닛 테스트 추가 / 수정
  • 통합 테스트 검증
  • 수동 테스트 완료

🚨 확인해야 할 사항 (Checklist)

PR을 제출하기 전에 아래 항목들을 확인해주세요.

  • 코드 포매팅 완료
  • 불필요한 파일/코드 제거
  • 로직 검증 완료
  • 프로젝트 빌드 성공
  • 린트/정적 분석 통과 (해당 시)

🙋 기타 참고 사항

리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.

Summary by CodeRabbit

  • 신기능

    • 이미지 사전 업로드 API(v2) 추가: 최대 3장 업로드 후 발급되는 imageKey로 모임 생성에 사용 가능
    • 모임 생성 쿨다운 적용: 연속 생성 시 대기시간이 요구되며 남은 시간 안내 제공
    • 이미지 처리 개선: WebP 변환 및 키 기반 이미지 참조로 V2 흐름 통합
  • 향상된 오류 처리

    • Redis·JSON 관련 오류에 대한 명확한 응답 및 대응 메시지 추가

✏️ Tip: You can customize this high-level summary in your review settings.

Redis에 임시 저장소를 사용하는 새로운 이미지 사전 업로드 흐름을 도입하여 클라이언트가 그룹 생성 전에 이미지를 업로드할 수 있도록 합니다.
악용 방지를 위해 Redis를 사용한 그룹 생성 쿨다운 기능을 추가합니다. 이 기능에는 실패 시 쿨다운 잠금을 해제하는 트랜잭션 롤백 처리가 포함됩니다.
사전 업로드된 이미지와 이미지 키를 활용하도록 그룹 생성 프로세스를 재구성하여 보안과 효율성을 향상시킵니다.
@LimdaeIl LimdaeIl self-assigned this Dec 18, 2025
Copilot AI review requested due to automatic review settings December 18, 2025 06:21
@LimdaeIl LimdaeIl added the ✨enhancement New feature or request label Dec 18, 2025
@LimdaeIl LimdaeIl moved this from Backlog to In progress in WeGo-Together Backend Dec 18, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

사전 업로드 이미지(v2)와 Redis 기반 모임 생성 쿨다운을 도입했습니다. 이미지 업로드는 WebP로 변환해 S3에 저장하고 메타를 Redis에 보관하며, 모임 생성 시 Redis에서 소비하고 소유자를 검증합니다. Redis 예외 및 JSON 예외 처리기가 추가되었습니다.

Changes

Cohort / File(s) 요약
예외 처리 및 에러 코드
src/main/java/team/wego/wegobackend/common/exception/GlobalExceptionHandler.java, src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java
Redis/JSON 예외 핸들러 추가(핸들러: Redis 관련, JsonProcessingException) 및 그룹 관련 7개 에러 코드 추가(쿨다운, 선 업로드 이미지, Redis 실패 등)
사전 업로드 이미지 DTO / 응답
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/PreUploadGroupImageV2Item.java, src/main/java/team/wego/wegobackend/group/v2/application/dto/common/PreUploadedGroupImage.java, src/main/java/team/wego/wegobackend/group/v2/application/dto/response/PreUploadGroupImageV2Response.java
선 업로드 이미지 모델(레코드)과 응답 DTO 추가
요청 DTO 변경/추가
src/main/java/team/wego/wegobackend/group/v2/application/dto/request/CreateGroupImageV2Request.java, src/main/java/team/wego/wegobackend/group/v2/application/dto/request/UpdateGroupImageItemV2Request.java
CreateGroupImageV2Request: URL 기반 → imageKey 기반으로 필드 변경 및 @Min 검증 추가; UpdateGroupImageItemV2Request 신규 추가
쿨다운 서비스
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupCreateCooldownService.java
Redis 키 기반 모임 생성 쿨다운 로직 추가(획득/해제, 트랜잭션 롤백 시 해제 등록)
사전 업로드 처리 서비스
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupImageV2PreUploadService.java
MultipartFile 최대 3개 검증, WebP 변환 및 S3 업로드 후 Redis에 PreUploadedGroupImage 저장하고 응답 반환
도메인 변경
src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupImageV2.java
imageKey 필드 추가(비업데이트, 길이/유니크), 팩토리/생성자 검증, changeSortOrder 추가
서비스 흐름 변경
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java
create 흐름에 쿨다운 획득(rollback-release) 추가 및 선 업로드 이미지(키) 소비·검증 후 이미지 생성/저장으로 전환
Redis 구성 및 저장소
src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisConfig.java, src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisRepository.java
PreUploadedGroupImage 전용 RedisTemplate 구성 및 TTL(2시간) 저장/조회/consume 구현
이미지 업로드 유틸
src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java
baseName 기반 WebP 변환+업로드 메서드 추가: uploadAsWebpWithSizeUsingBaseName
컨트롤러 및 API 변경
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupImageV2Controller.java, src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java
이미지 선업로드 엔드포인트 추가(/api/v2/groups/images/upload), 그룹 생성 핸들러명 변경(메서드명: create) 및 V2 흐름 반영
테스트 스크립트
src/test/http/group/v2/v2-group-create.http
V1→V2 전환: 이미지 참조를 URL에서 imageKey로 변경하고 시나리오 및 변수명 업데이트

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client
    participant GroupImageV2Controller
    participant GroupImageV2PreUploadService
    participant ImageUploadService
    participant S3
    participant PreUploadedGroupImageRedisRepository
    participant Redis

    User->>Client: POST /api/v2/groups/images/upload (files)
    Client->>GroupImageV2Controller: uploadImages(userDetails, images)
    GroupImageV2Controller->>GroupImageV2PreUploadService: uploadGroupImagesV2(uploaderId, images)

    loop for each file
        GroupImageV2PreUploadService->>ImageUploadService: uploadAsWebpWithSizeUsingBaseName(file, baseName, 440x240)
        ImageUploadService->>S3: PUT webp 440x240
        S3-->>ImageUploadService: URL
        GroupImageV2PreUploadService->>ImageUploadService: uploadAsWebpWithSizeUsingBaseName(file, baseName, 100x100)
        ImageUploadService->>S3: PUT webp 100x100
        S3-->>ImageUploadService: URL
        GroupImageV2PreUploadService->>PreUploadedGroupImageRedisRepository: save(PreUploadedGroupImage)
        PreUploadedGroupImageRedisRepository->>Redis: SET key (TTL 2h)
        Redis-->>PreUploadedGroupImageRedisRepository: OK
    end

    GroupImageV2PreUploadService-->>GroupImageV2Controller: PreUploadGroupImageV2Response (imageKeys)
    GroupImageV2Controller-->>Client: 201 Created
Loading
sequenceDiagram
    actor User
    participant Client
    participant GroupV2Controller
    participant GroupV2Service
    participant GroupCreateCooldownService
    participant PreUploadedGroupImageRedisRepository
    participant Redis
    participant Database

    User->>Client: POST /api/v2/groups/create (request, cooldownSeconds, imageKeys)
    Client->>GroupV2Controller: create(userDetails, request, cooldownSeconds)
    GroupV2Controller->>GroupV2Service: create(userId, request, cooldownSeconds)

    GroupV2Service->>GroupCreateCooldownService: acquireOrThrowWithRollbackRelease(userId, cooldownSeconds)
    GroupCreateCooldownService->>Redis: SET key NX EX cooldownSeconds

    alt key existed
        Redis-->>GroupCreateCooldownService: exists
        GroupCreateCooldownService-->>GroupV2Service: throw GROUP_CREATE_COOLDOWN_ACTIVE
        GroupV2Service-->>Client: 429 Too Many Requests
    else key set
        Redis-->>GroupCreateCooldownService: OK
        GroupCreateCooldownService-->>GroupV2Service: proceed

        loop for each imageKey
            GroupV2Service->>PreUploadedGroupImageRedisRepository: consume(imageKey)
            PreUploadedGroupImageRedisRepository->>Redis: GETDEL key
            Redis-->>PreUploadedGroupImageRedisRepository: PreUploadedGroupImage
            PreUploadedGroupImageRedisRepository-->>GroupV2Service: Optional<PreUploadedGroupImage>
            alt missing or owner mismatch
                GroupV2Service-->>Client: error (PRE_UPLOADED_IMAGE_NOT_FOUND / OWNER_MISMATCH)
            else valid
                GroupV2Service->>Database: persist group image entity (with imageKey and URLs)
            end
        end

        GroupV2Service->>Database: persist group
        Database-->>GroupV2Service: OK
        GroupV2Service-->>Client: 201 Created
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

검토 시 주의할 파일/영역:

  • GroupV2Service.java — 쿨다운 획득/롤백 동기화와 선 업로드 이미지 소비/검증 로직
  • GroupCreateCooldownService.java — Redis NX/TTL 처리 및 트랜잭션 동작 보장
  • PreUploadedGroupImageRedisRepository / PreUploadedGroupImageRedisConfig — 직렬화 설정, consume 원자성
  • GroupImageV2PreUploadService.java & ImageUploadService.java — WebP 변환·S3 업로드 흐름과 실패 시 정합성(업로드된 파일 vs Redis 저장)
  • GlobalExceptionHandler.java — 새 핸들러가 전체 예외 매핑에 미치는 영향

Possibly related PRs

Poem

🐰 나는 작은 토끼, 파일을 꿀꺽했네
키 하나 챙겨 Redis 창고에 맡겼네
삼십 초 숨 고르기, 쿨다운은 든든해
모임 만들 땐 꺼내 쓰고 웃음 짓네 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경 사항을 명확하게 반영합니다: 모임 생성 쿨다운과 모임 이미지 V2 구현이 정확히 일치합니다.
Linked Issues check ✅ Passed 모든 연결된 이슈의 목표가 충족되었습니다: Redis 기반 쿨다운 기능(기본값 30초), 이미지 사전 업로드 흐름, 그룹 생성 프로세스 재구성.
Out of Scope Changes check ✅ Passed 모든 변경이 범위 내에 있습니다: 쿨다운, 이미지 V2 흐름, Redis 저장소, 예외 처리 등이 이슈 #134의 목표와 일치합니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 633493e and 0e71d80.

📒 Files selected for processing (4)
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/request/CreateGroupImageV2Request.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (6 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (2 hunks)
  • src/test/http/group/v2/v2-group-create.http (10 hunks)

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.

Copy link

@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: 6

🧹 Nitpick comments (10)
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/PreUploadedGroupImage.java (1)

6-14: serialVersionUID 추가를 고려하세요 (선택 사항).

Redis에 저장되는 객체에 serialVersionUID를 명시하면 향후 필드 변경 시 역직렬화 호환성을 보장할 수 있습니다. 임시 캐시 용도이므로 필수는 아니지만, 추가하면 안정성이 향상됩니다.

🔎 제안 수정사항
 public record PreUploadedGroupImage(
         String imageKey,
         Long uploaderId,
         String url440x240,
         String url100x100,
         LocalDateTime createdAt
 ) implements Serializable {
+    private static final long serialVersionUID = 1L;
 
 }
src/main/java/team/wego/wegobackend/common/exception/GlobalExceptionHandler.java (1)

285-295: 도메인별 에러 코드 사용을 재고하세요.

전역 예외 핸들러에서 GroupErrorCode.REDIS_READ_FAILED를 사용하고 있습니다. 고려사항:

  1. DataAccessException은 Redis뿐만 아니라 모든 데이터 액세스 계층 예외를 포함할 수 있습니다
  2. 전역 핸들러에서 도메인별 에러 코드를 사용하면 결합도가 높아집니다

AppErrorCode에 범용적인 REDIS_CONNECTION_FAILED 또는 DATA_ACCESS_ERROR를 추가하는 것을 고려해보세요.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupImageV2PreUploadService.java (2)

38-64: 빈 파일 건너뛰기 시 sortOrder 불일치 가능성

빈 파일을 continue로 건너뛰면 반환되는 result 리스트의 인덱스와 sortOrder(= i)가 요청 이미지 리스트의 원래 위치와 일치하지 않을 수 있습니다. 예를 들어, [file0, empty, file2]를 업로드하면 결과는 [{sortOrder:0}, {sortOrder:2}]가 됩니다.

클라이언트가 이 동작을 예상한다면 문제없지만, 그렇지 않으면 혼란을 줄 수 있습니다. 빈 파일에 대해 예외를 던지거나, 건너뛴 후 인덱스를 재조정하는 방안을 고려해 보세요.


49-61: 부분 실패 시 S3 정리 누락 가능성

S3 업로드가 루프 중간에 실패하면 이미 업로드된 이미지들이 고아 상태로 남을 수 있습니다. Redis에는 저장되지 않았지만 S3에는 이미 존재하는 상황이 발생할 수 있습니다.

현재 구조에서는 큰 문제가 아닐 수 있지만, 운영 환경에서 S3 스토리지 비용이 누적될 수 있습니다. 필요시 try-catch로 감싸서 실패 시 이전 업로드를 정리하는 보상 로직을 추가할 수 있습니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (1)

210-238: 이미지 소비 후 생성 실패 시 복구 불가

preUploadedGroupImageRedisRepository.consume()이 Redis에서 이미지를 삭제하는데, 이후 모임 생성이 실패하면(예: 검증 오류) 사용자가 업로드한 이미지를 다시 사용할 수 없습니다. S3에는 이미지가 남아 있지만 Redis 참조가 사라져서 사용자는 이미지를 다시 업로드해야 합니다.

이미지 유효성 검증을 먼저 수행하고(find 사용), 모든 검증이 완료된 후에 한꺼번에 consume하는 방식을 고려해 보세요.

🔎 수정 제안:
 // 이미지 생성
 if (request.images() != null && !request.images().isEmpty()) {
+    // 1단계: 모든 이미지 검증 (consume 하지 않음)
+    List<PreUploadedGroupImage> validatedImages = new ArrayList<>();
     for (int i = 0; i < request.images().size(); i++) {
         CreateGroupImageV2Request imgReq = request.images().get(i);
-
         String imageKey = imgReq.imageKey();
         if (imageKey == null || imageKey.isBlank()) {
             throw new GroupException(GroupErrorCode.GROUP_IMAGE_KEY_REQUIRED);
         }
-
-        int sortOrder = (imgReq.sortOrder() != null) ? imgReq.sortOrder() : i;
-
-        PreUploadedGroupImage pre = preUploadedGroupImageRedisRepository.consume(imageKey)
+        PreUploadedGroupImage pre = preUploadedGroupImageRedisRepository.find(imageKey)
                 .orElseThrow(() -> new GroupException(
                         GroupErrorCode.PRE_UPLOADED_IMAGE_NOT_FOUND, imageKey
                 ));
-
         if (!userId.equals(pre.uploaderId())) {
             throw new GroupException(GroupErrorCode.PRE_UPLOADED_IMAGE_OWNER_MISMATCH,
                     imageKey);
         }
+        validatedImages.add(pre);
+    }
 
+    // 2단계: 검증 완료 후 consume 및 생성
+    for (int i = 0; i < validatedImages.size(); i++) {
+        PreUploadedGroupImage pre = validatedImages.get(i);
+        CreateGroupImageV2Request imgReq = request.images().get(i);
+        int sortOrder = (imgReq.sortOrder() != null) ? imgReq.sortOrder() : i;
+        
+        preUploadedGroupImageRedisRepository.consume(pre.imageKey());
+        
         GroupImageV2.create(
                 group,
                 sortOrder,
src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisRepository.java (4)

19-21: imageKey에 대한 입력 검증 추가를 고려하세요.

imageKey가 null이거나 빈 문자열인 경우 예상치 못한 Redis 키가 생성될 수 있습니다. 방어적 프로그래밍 관점에서 검증을 추가하는 것이 좋습니다.

🔎 제안하는 수정사항 보기
 private String key(String imageKey) {
+    if (imageKey == null || imageKey.isBlank()) {
+        throw new IllegalArgumentException("imageKey는 null이거나 비어있을 수 없습니다.");
+    }
     return PREFIX + imageKey;
 }

23-29: Redis 연산에 대한 예외 처리 추가를 고려하세요.

Redis 연결 실패나 직렬화 오류 등의 예외가 발생할 수 있습니다. GroupErrorCode.REDIS_SAVE_FAILED가 정의되어 있으므로, 이를 활용한 예외 처리를 추가하는 것이 좋습니다.

🔎 제안하는 수정사항 보기
 public void save(PreUploadedGroupImage value) {
+    try {
         preUploadedGroupImageRedisTemplate.opsForValue().set(
                 key(value.imageKey()),
                 value,
                 TTL
         );
+    } catch (Exception e) {
+        throw new GroupException(GroupErrorCode.REDIS_SAVE_FAILED, e);
+    }
 }

31-35: Redis 연산에 대한 예외 처리 추가를 고려하세요.

Redis 연결 실패나 역직렬화 오류 등의 예외가 발생할 수 있습니다. GroupErrorCode.REDIS_READ_FAILED를 활용한 예외 처리를 추가하는 것이 좋습니다.

🔎 제안하는 수정사항 보기
 public Optional<PreUploadedGroupImage> find(String imageKey) {
+    try {
         PreUploadedGroupImage value =
                 preUploadedGroupImageRedisTemplate.opsForValue().get(key(imageKey));
         return Optional.ofNullable(value);
+    } catch (Exception e) {
+        throw new GroupException(GroupErrorCode.REDIS_READ_FAILED, e);
+    }
 }

37-41: Redis 연산에 대한 예외 처리 추가를 고려하세요.

getAndDelete()는 원자적 연산으로 동시성 측면에서 안전하지만, Redis 연결 실패나 역직렬화 오류에 대한 예외 처리가 필요합니다.

🔎 제안하는 수정사항 보기
 public Optional<PreUploadedGroupImage> consume(String imageKey) {
+    try {
         PreUploadedGroupImage value =
                 preUploadedGroupImageRedisTemplate.opsForValue().getAndDelete(key(imageKey));
         return Optional.ofNullable(value);
+    } catch (Exception e) {
+        throw new GroupException(GroupErrorCode.REDIS_READ_FAILED, e);
+    }
 }
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java (1)

12-18: 관련 에러 코드들을 그룹화하는 것을 고려하세요.

새로운 에러 코드들이 enum 상단에 추가되었는데, 유지보수성 향상을 위해 관련된 에러 코드들을 함께 배치하는 것이 좋습니다. 예를 들어:

  • 쿨다운 관련: INVALID_COOLDOWN_SECONDS, GROUP_CREATE_COOLDOWN_ACTIVE
  • 이미지 관련: PRE_UPLOADED_IMAGE_*, GROUP_IMAGE_KEY_REQUIRED와 기존 이미지 에러들 (Line 19-22, 28, 30, 44-45)
  • Redis 관련: REDIS_SAVE_FAILED, REDIS_READ_FAILED
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8396aef and 633493e.

📒 Files selected for processing (17)
  • src/main/java/team/wego/wegobackend/common/exception/GlobalExceptionHandler.java (11 hunks)
  • src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/common/PreUploadGroupImageV2Item.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/common/PreUploadedGroupImage.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/request/CreateGroupImageV2Request.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/request/UpdateGroupImageItemV2Request.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/PreUploadGroupImageV2Response.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupCreateCooldownService.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupImageV2PreUploadService.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (5 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupImageV2.java (4 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisConfig.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisRepository.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/presentation/GroupImageV2Controller.java (1 hunks)
  • src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (2 hunks)
  • src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java (3 hunks)
  • src/test/http/group/v2/v2-group-create.http (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisRepository.java (2)
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (1)
  • RequiredArgsConstructor (40-365)
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (1)
  • RequiredArgsConstructor (27-106)
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupImageV2PreUploadService.java (3)
src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisRepository.java (1)
  • RequiredArgsConstructor (10-42)
src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java (1)
  • RequiredArgsConstructor (29-493)
src/main/java/team/wego/wegobackend/group/domain/exception/GroupException.java (1)
  • GroupException (6-15)
🔇 Additional comments (16)
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/PreUploadGroupImageV2Item.java (1)

3-10: LGTM! 깔끔한 DTO 구조입니다.

사전 업로드된 이미지 메타데이터를 전달하는 용도로 적절한 구조이며, record 타입 사용이 적합합니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/PreUploadGroupImageV2Response.java (1)

6-10: LGTM! 응답 DTO 구조가 적절합니다.

사전 업로드된 이미지 목록을 반환하는 용도로 깔끔하게 설계되었습니다.

src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupImageV2.java (4)

42-43: imageKey 필드 제약조건이 적절합니다.

UUID 형식의 고유 키를 위한 제약조건(길이 36, 수정 불가, 고유성)이 올바르게 설정되었습니다.


55-63: 생성자 검증 로직이 적절합니다.

imageKey에 대한 null/blank 검증이 추가되어 데이터 무결성이 보장됩니다.


65-93: 팩토리 메서드 업데이트가 올바릅니다.

imageKey 파라미터 추가와 검증이 적절하게 구현되었습니다. 팩토리와 생성자에 중복 검증이 있지만, 방어적 프로그래밍 관점에서 문제없습니다.


111-113: changeSortOrder 메서드는 현재 사용되지 않는 미사용 코드입니다.

코드 분석 결과, changeSortOrder 메서드는 어디에서도 호출되지 않고 있습니다. 서비스 계층, 컨트롤러, 테스트 코드 등 전체 코드베이스에서 이 메서드의 사용 사례가 없으며, 그룹 이미지의 sortOrder는 생성 시점에만 설정됩니다. 따라서 원래 지적된 서비스 계층의 제약조건 검증 부재에 대한 우려는 현재 코드에서는 발생하지 않습니다. 이 메서드는 제거하거나, 향후 사용 계획이 있다면 적절한 유효성 검사 로직을 추가하여 구현해야 합니다.

Likely an incorrect or invalid review comment.

src/main/java/team/wego/wegobackend/group/v2/application/dto/request/UpdateGroupImageItemV2Request.java (1)

3-9: UpdateGroupImageItemV2Request는 현재 코드베이스에서 사용되지 않습니다.

이 DTO 레코드는 정의되어 있지만 어떤 서비스, 컨트롤러, 또는 테스트에서도 사용되지 않습니다. 따라서 제안된 검증 로직을 확인할 대상이 없습니다. 이 클래스가 실제로 사용될 때 상호 배타적 검증(id와 imageKey의 동시 존재 방지)을 고려해야 합니다.

Likely an incorrect or invalid review comment.

src/main/java/team/wego/wegobackend/image/application/service/ImageUploadService.java (2)

390-398: LGTM!

포맷팅 변경으로 가독성이 개선되었습니다. 기능 변경 없이 코드 스타일만 정리되었습니다.

Also applies to: 411-413


475-491: LGTM!

기존 패턴을 잘 따르고 있으며, 외부에서 제공된 baseName을 사용하여 S3 키를 생성하는 방식이 V2 사전 업로드 흐름에 적합합니다. 입력 파일에 대한 검증(크기, 콘텐츠 타입, 확장자)이 적절히 수행됩니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupImageV2PreUploadService.java (1)

69-76: LGTM!

최대 3개 이미지 제한 검증이 명확하게 구현되었습니다.

src/test/http/group/v2/v2-group-create.http (1)

29-51: LGTM!

V2 이미지 사전 업로드 및 imageKey 추출 흐름이 올바르게 구현되었습니다.

src/main/java/team/wego/wegobackend/group/v2/infrastructure/redis/PreUploadedGroupImageRedisConfig.java (1)

15-38: LGTM!

Redis 템플릿 구성이 올바르게 되어 있습니다. 애플리케이션의 ObjectMapper를 재사용하여 일관된 직렬화 설정을 보장합니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupCreateCooldownService.java (2)

25-55: LGTM!

setIfAbsent를 사용한 원자적 잠금 획득과 TTL 기반 남은 시간 계산이 올바르게 구현되었습니다. TTL이 -2(키 없음) 또는 -1(만료 없음)인 경우의 방어 처리도 적절합니다.


64-80: LGTM!

트랜잭션 롤백 시 쿨다운 잠금을 해제하는 패턴이 잘 구현되었습니다. 이를 통해 모임 생성이 실패해도 사용자가 쿨다운에 갇히지 않습니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (2)

170-177: LGTM!

쿨다운 획득을 DB 작업 전에 수행하고, 롤백 시 자동 해제되도록 구성한 것이 적절합니다.


231-237: GroupImageV2에 저장되는 imageKey가 UUID이므로 나중에 S3 이미지 삭제 시 문제가 될 수 있습니다.

pre.imageKey()는 UUID이지만, S3에 실제로 저장되는 키는 baseName + "_" + size + ".webp" 형식(예: 20231215143022_0_<uuid>_440x240.webp)입니다. GroupImageV2.imageKey에 UUID만 저장하면 나중에 GroupImageV2가 삭제될 때 S3에서 이미지를 제대로 삭제할 수 없습니다. imageKey는 S3 삭제에 필요한 전체 키 경로를 저장하거나, variant의 URL에서 키를 추출하는 방식으로 개선이 필요합니다.

Comment on lines +297 to 302
@ExceptionHandler(JsonProcessingException.class)
public ResponseEntity<ErrorResponse> handleJson(JsonProcessingException ex, HttpServletRequest request) {
log.error("Jackson 직렬화/역직렬화 실패(500): {}", rootCauseMessage(ex), ex);
AppException mapped = new AppException(GroupErrorCode.REDIS_READ_FAILED);
return handleApp(mapped, request);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

JSON 처리 오류에 부적절한 에러 코드가 사용되고 있습니다.

JsonProcessingExceptionGroupErrorCode.REDIS_READ_FAILED로 매핑하는 것은 의미상 부정확합니다. JSON 처리 오류는 Redis와 무관한 컨텍스트(일반 API 응답 직렬화 등)에서도 발생할 수 있습니다.

다음 중 하나를 선택하세요:

  1. AppErrorCodeJSON_PROCESSING_ERROR 같은 별도의 에러 코드 추가
  2. 이 핸들러가 Redis 관련 JSON 오류만 처리하도록 의도된 경우, 더 구체적인 예외 타입으로 범위를 좁히거나 주석으로 명확히 설명
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/common/exception/GlobalExceptionHandler.java
around lines 297 to 302, the JsonProcessingException is incorrectly mapped to
GroupErrorCode.REDIS_READ_FAILED; instead add a new error code (e.g.,
AppErrorCode.JSON_PROCESSING_ERROR) to the appropriate enum or error-code class,
update any centralized error metadata (message, HTTP status) for that code, and
change this handler to map JsonProcessingException to the new
JSON_PROCESSING_ERROR; if you prefer not to add a new code, narrow the handler
to Redis-specific JSON exceptions and document intent—either way ensure tests
and any error-to-status mappings are updated accordingly.

public enum GroupErrorCode implements ErrorCode {

INVALID_COOLDOWN_SECONDS(HttpStatus.BAD_REQUEST, "모임: 유효하지 않은 쿨다운 정책입니다."),
GROUP_CREATE_COOLDOWN_ACTIVE(HttpStatus.TOO_MANY_REQUESTS, "모임: 모임 생성은 연속으로 할 수 없습니다. {%s}초 후 다시 시도해 주세요."),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

메시지 템플릿 포맷 오류를 수정하세요.

{%s} 형식은 String.format()과 호환되지 않습니다. 다른 에러 코드들과 동일하게 %s만 사용해야 합니다.

🔎 제안하는 수정사항 보기
-    GROUP_CREATE_COOLDOWN_ACTIVE(HttpStatus.TOO_MANY_REQUESTS, "모임: 모임 생성은 연속으로 할 수 없습니다. {%s}초 후 다시 시도해 주세요."),
+    GROUP_CREATE_COOLDOWN_ACTIVE(HttpStatus.TOO_MANY_REQUESTS, "모임: 모임 생성은 연속으로 할 수 없습니다. %s초 후 다시 시도해 주세요."),
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java
around line 13, the message template uses "{%s}" which is not compatible with
String.format; replace the template token with "%s" to match other error codes
and ensure String.format will interpolate the cooldown seconds correctly.

Comment on lines 3 to 6
public record CreateGroupImageV2Request(
Integer sortOrder,
String imageUrl440x240,
String imageUrl100x100
String imageKey,
Integer sortOrder
) {}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

입력 검증 어노테이션을 추가하세요.

요청 DTO에 필수 검증이 누락되어 있습니다:

  • imageKey: @NotBlank 및 길이 제약 (예: @Size(max=36)) 추가 필요
  • sortOrder: @NotNull 및 범위 제약 (예: @Min(0)) 추가 권장

서비스 계층에 도달하기 전에 잘못된 입력을 조기에 차단할 수 있습니다.

🔎 제안 수정사항
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.Size;
+
 public record CreateGroupImageV2Request(
+        @NotBlank(message = "이미지 키는 필수입니다")
+        @Size(max = 36, message = "이미지 키는 최대 36자입니다")
         String imageKey,
+        @NotNull(message = "정렬 순서는 필수입니다")
+        @Min(value = 0, message = "정렬 순서는 0 이상이어야 합니다")
         Integer sortOrder
 ) {}
📝 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 record CreateGroupImageV2Request(
Integer sortOrder,
String imageUrl440x240,
String imageUrl100x100
String imageKey,
Integer sortOrder
) {}
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
public record CreateGroupImageV2Request(
@NotBlank(message = "이미지 키는 필수입니다")
@Size(max = 36, message = "이미지 키는 최대 36자입니다")
String imageKey,
@NotNull(message = "정렬 순서는 필수입니다")
@Min(value = 0, message = "정렬 순서는 0 이상이어야 합니다")
Integer sortOrder
) {}
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/application/dto/request/CreateGroupImageV2Request.java
around lines 3 to 6, the request DTO lacks validation annotations; add @NotBlank
and @Size(max=36) to the imageKey component and add @NotNull and @Min(0) to the
sortOrder component (use the project's validation package - javax.validation or
jakarta.validation - and add the necessary imports) so invalid requests are
rejected before reaching the service layer.

Comment on lines +26 to +37
@PostMapping("/upload")
public ResponseEntity<ApiResponse<PreUploadGroupImageV2Response>> uploadImages(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam("images") List<MultipartFile> images
) {
PreUploadGroupImageV2Response response = preUploadService.uploadGroupImagesV2(
userDetails.getId(), images);

return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success(HttpStatus.CREATED.value(), response));
}
Copy link

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:

#!/bin/bash
# Find the GroupImageV2Controller file and examine surrounding context
find . -type f -name "*GroupImageV2*" 2>/dev/null | head -20

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 916


🏁 Script executed:

#!/bin/bash
# Search for the preUploadService implementation and validation logic
find . -type f -name "*.java" -path "*/service/*" 2>/dev/null | grep -i "upload\|image" | head -20

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 348


🏁 Script executed:

#!/bin/bash
# Search for @Valid, @Validated, and OpenAPI annotations usage in codebase
rg "@Valid|@Validated|@Operation|@Parameter|@Schema" --type java -l 2>/dev/null | head -20

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 878


🏁 Script executed:

#!/bin/bash
# Read the full GroupImageV2Controller.java file
cat -n src/main/java/team/wego/wegobackend/group/v2/presentation/GroupImageV2Controller.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1965


🏁 Script executed:

#!/bin/bash
# Read the GroupImageV2PreUploadService.java to check validation logic
cat -n src/main/java/team/wego/wegobackend/group/v2/application/service/GroupImageV2PreUploadService.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 3644


🏁 Script executed:

#!/bin/bash
# Check if GroupImageV2ControllerDocs exists
find . -type f -name "*GroupImageV2*Docs*" 2>/dev/null

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check the structure and annotations used in v1 GroupImageControllerDocs for reference
head -50 src/main/java/team/wego/wegobackend/group/presentation/v1/GroupImageControllerDocs.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 2280


파일 업로드 검증 및 API 문서화를 추가하세요.

현재 엔드포인트에 다음 항목들이 누락되어 있습니다:

  1. 컨트롤러 수준 검증 부재: 서비스 계층에서 최대 3개 파일 제한을 검증하지만, 컨트롤러 수준에서 조기 검증이 없어 불필요한 요청 처리가 발생합니다. @Valid와 커스텀 validator를 추가하여 컨트롤러에서 먼저 검증하세요.

  2. API 문서화 누락: @Operation, @Parameter, @Tag 같은 OpenAPI 어노테이션이 없으며, v1 버전처럼 별도 GroupImageV2ControllerDocs 인터페이스도 없습니다. API 사용자가 3개 파일 제한 같은 제약사항을 알기 어렵습니다.

🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupImageV2Controller.java
around lines 26-37, add controller-level validation and OpenAPI docs: create a
request wrapper DTO (e.g., GroupImageUploadRequest) holding List<MultipartFile>
images annotated with @NotNull and @Size(max=3) or use a custom Constraint
(implement ConstraintValidator to check file count and optionally file
content/type/size), annotate the controller parameter with @Valid to enforce
early validation and return 400 on violations; add OpenAPI annotations (@Tag on
the controller, @Operation on the upload method, and @Parameter describing the
images field and the 3-file limit) or extract docs into a
GroupImageV2ControllerDocs interface (like v1) and have the controller implement
it so API docs clearly show the 3-file constraint and request details.

Comment on lines 34 to 40
@PostMapping("/create")
public ResponseEntity<ApiResponse<CreateGroupV2Response>> createGroup(
public ResponseEntity<ApiResponse<CreateGroupV2Response>> create(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam(defaultValue = "30") int cooldownSeconds,
@RequestBody @Valid CreateGroupV2Request request
) {
CreateGroupV2Response response = groupV2Service.create(userDetails.getId(), request);
CreateGroupV2Response response = groupV2Service.create(userDetails.getId(), request, cooldownSeconds);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

보안 우려: cooldownSeconds를 클라이언트가 제어 가능

cooldownSeconds@RequestParam으로 노출하면 악의적인 사용자가 ?cooldownSeconds=1과 같이 매우 낮은 값을 설정하여 쿨다운 메커니즘을 우회할 수 있습니다. 이는 PR 목표인 "악용 방지를 위한 쿨다운 기능"의 효과를 무력화합니다.

쿨다운 시간은 서버 측 설정(@Value 또는 @ConfigurationProperties)으로 관리하고, 클라이언트 요청에서 제거하는 것이 좋습니다.

🔎 수정 제안:
+@Value("${group.create.cooldown-seconds:30}")
+private int cooldownSeconds;

 @PostMapping("/create")
 public ResponseEntity<ApiResponse<CreateGroupV2Response>> create(
         @AuthenticationPrincipal CustomUserDetails userDetails,
-        @RequestParam(defaultValue = "30") int cooldownSeconds,
         @RequestBody @Valid CreateGroupV2Request request
 ) {
     CreateGroupV2Response response = groupV2Service.create(userDetails.getId(), request, cooldownSeconds);
📝 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
@PostMapping("/create")
public ResponseEntity<ApiResponse<CreateGroupV2Response>> createGroup(
public ResponseEntity<ApiResponse<CreateGroupV2Response>> create(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam(defaultValue = "30") int cooldownSeconds,
@RequestBody @Valid CreateGroupV2Request request
) {
CreateGroupV2Response response = groupV2Service.create(userDetails.getId(), request);
CreateGroupV2Response response = groupV2Service.create(userDetails.getId(), request, cooldownSeconds);
@Value("${group.create.cooldown-seconds:30}")
private int cooldownSeconds;
@PostMapping("/create")
public ResponseEntity<ApiResponse<CreateGroupV2Response>> create(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody @Valid CreateGroupV2Request request
) {
CreateGroupV2Response response = groupV2Service.create(userDetails.getId(), request, cooldownSeconds);
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java
around lines 34 to 40, remove the client-controlled @RequestParam
cooldownSeconds and instead load the cooldown from server configuration (either
with @Value or a @ConfigurationProperties bean); update the controller method
signature to no longer accept cooldownSeconds from the request and pass the
server-side value into groupV2Service.create; update any affected service method
signature/usage to accept the configured cooldown (with a sensible
default/fallback) and add validation/logging as needed so clients cannot
override the cooldown.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a new V2 image pre-upload flow using Redis as temporary storage and adds a group creation cooldown feature to prevent abuse. The changes refactor group creation to use image keys instead of direct URLs, improving security and separation of concerns.

  • Introduces Redis-based pre-upload workflow where clients upload images before group creation
  • Adds rate-limiting via cooldown mechanism with transaction rollback support
  • Refactors group creation API to use imageKey references instead of direct image URLs

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
v2-group-create.http Updated test cases to use V2 endpoints and imageKey instead of URLs
GroupV2Controller.java Added cooldownSeconds parameter to create endpoint
GroupImageV2Controller.java New controller for V2 image pre-upload endpoint
PreUploadedGroupImageRedisRepository.java Redis repository for managing pre-uploaded images with 2-hour TTL
PreUploadedGroupImageRedisConfig.java Redis configuration for pre-uploaded image storage
GroupImageV2.java Added imageKey field with unique constraint to entity
GroupV2Service.java Integrated cooldown service and image consumption from Redis
GroupImageV2PreUploadService.java Service for pre-uploading and storing images in Redis
GroupCreateCooldownService.java Service implementing cooldown logic with rollback support
ImageUploadService.java Added new method for WebP upload with custom base name
PreUploadGroupImageV2Response.java Response DTO for pre-upload operation
UpdateGroupImageItemV2Request.java Request DTO for updating group images (new file)
CreateGroupImageV2Request.java Refactored to use imageKey instead of URLs
PreUploadedGroupImage.java Domain object for Redis-stored pre-uploaded image data
PreUploadGroupImageV2Item.java Response item for pre-uploaded images
GroupErrorCode.java Added error codes for cooldown and pre-upload validation
GlobalExceptionHandler.java Added handlers for Redis and JSON exceptions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +67 to +78
// create()는 @Transactional 안에서 호출되니까 보통 true
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
if (status == STATUS_ROLLED_BACK) {
release(userId);
}
}
}
);
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The comment states "create()는 @transactional 안에서 호출되니까 보통 true" (usually true because create() is called within @transactional), but this makes a dangerous assumption. If the calling context changes or if this method is reused elsewhere without a transaction, the rollback synchronization won't be registered. Consider checking the transaction status and either throwing an exception or logging a warning if no transaction is active, to prevent silent failures in cooldown release.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +45



Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The file ends with three blank lines. According to typical Java style conventions, files should end with a single newline. Consider removing the extra blank lines for consistency.

Suggested change

Copilot uses AI. Check for mistakes.

@PostMapping("/create")
public ResponseEntity<ApiResponse<CreateGroupV2Response>> createGroup(
public ResponseEntity<ApiResponse<CreateGroupV2Response>> create(
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The method name was changed from "createGroup" to just "create". While this may work, it's less descriptive and could be confusing when reading code or logs. The previous name "createGroup" was more explicit about what the method does. Consider reverting to the more descriptive name or document the reason for this change.

Suggested change
public ResponseEntity<ApiResponse<CreateGroupV2Response>> create(
public ResponseEntity<ApiResponse<CreateGroupV2Response>> createGroup(

Copilot uses AI. Check for mistakes.
@Column(name = "group_image_id", nullable = false, updatable = false)
private Long id;

@Column(name = "image_key", nullable = false, updatable = false, length = 36, unique = true)
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The imageKey column is marked as unique, but there's a potential race condition. If the same pre-uploaded image is consumed multiple times (e.g., due to retries or concurrent requests), this will cause a unique constraint violation. Consider adding proper synchronization or handling this exception gracefully in the service layer.

Suggested change
@Column(name = "image_key", nullable = false, updatable = false, length = 36, unique = true)
@Column(name = "image_key", nullable = false, updatable = false, length = 36)

Copilot uses AI. Check for mistakes.
throw new GroupException(GroupErrorCode.USER_ID_NULL);
}

groupCreateCooldownService.acquireOrThrowWithRollbackRelease(userId, cooldownSeconds);
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The cooldown acquire operation occurs before the transaction starts. If the transaction processing takes a long time or if there are multiple retries, the cooldown lock may expire before the transaction completes. This could lead to inconsistent state where the cooldown is released but the group is still being created. Consider acquiring the cooldown lock after basic validation but ensuring it's held for the duration of the critical operations.

Copilot uses AI. Check for mistakes.
Comment on lines 102 to +105



Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The file ends with three blank lines. According to typical Java style conventions, files should end with a single newline. Consider removing the extra blank lines for consistency.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +47
String imageKey = UUID.randomUUID().toString();
String baseName =
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+ "_" + i + "_" + imageKey;
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The imageKey is generated as a UUID but then included in the S3 key's baseName along with a timestamp and index. This could allow an attacker to predict the S3 key structure if they know the upload time. While the UUID provides randomness, consider whether exposing the timestamp pattern is acceptable for your security requirements, or use only the UUID for the S3 key to prevent any information leakage about upload timing.

Copilot uses AI. Check for mistakes.
"sortOrder": 1,
"imageUrl440x240": "{{img1_main}}",
"imageUrl100x100": "{{img1_thumb}}"
"imageKey": "{{img1_key}}"ㄱ
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

There is a stray Korean character 'ㄱ' at the end of this line. This appears to be a typo that should be removed.

Suggested change
"imageKey": "{{img1_key}}"
"imageKey": "{{img1_key}}"

Copilot uses AI. Check for mistakes.
public ResponseEntity<ApiResponse<CreateGroupV2Response>> createGroup(
public ResponseEntity<ApiResponse<CreateGroupV2Response>> create(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam(defaultValue = "30") int cooldownSeconds,
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

Using a request parameter for cooldownSeconds in a production endpoint is a security risk. This allows clients to bypass or reduce the cooldown protection by passing different values (e.g., cooldownSeconds=0 or cooldownSeconds=1). The cooldown duration should be configured server-side only, not client-controlled. Consider removing this parameter and using a fixed configuration value instead.

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +224
PreUploadedGroupImage pre = preUploadedGroupImageRedisRepository.consume(imageKey)
.orElseThrow(() -> new GroupException(
GroupErrorCode.PRE_UPLOADED_IMAGE_NOT_FOUND, imageKey
));
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

Using consume() removes the image from Redis, but if the transaction fails after consuming one or more images, those images are lost and cannot be used in a retry. This breaks idempotency and could frustrate users who encounter transaction failures. Consider using find() instead of consume() and only removing the images after successful transaction commit, or implement a mechanism to restore consumed images on transaction rollback.

Copilot uses AI. Check for mistakes.
사용자가 그룹을 너무 자주 생성하는 것을 방지하기 위해 쿨다운 기간을 구현합니다.

이 변경 사항은 그룹 생성 시도 후 30초의 고정 쿨다운 기간을 도입하여
시스템 안정성을 향상시키고 악용 가능성을 줄입니다.
쿨다운 시간은 이제 서비스 계층 내에서 상수로 관리됩니다.
또한 그룹 이미지의 정렬 순서가 음수가 아닌지 확인합니다.
@LimdaeIl LimdaeIl merged commit a011cab into main Dec 18, 2025
1 check was pending
@LimdaeIl LimdaeIl deleted the feat/image-upload-v2-cooldown branch December 18, 2025 06:34
@github-project-automation github-project-automation bot moved this from In progress to Done in WeGo-Together Backend Dec 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEAT] 모임 생성 쿨다운과 모임 이미지 V2 구현

1 participant