Skip to content

Comments

Feature/event#7

Merged
naooung merged 13 commits intomainfrom
feature/event
Dec 12, 2025
Merged

Feature/event#7
naooung merged 13 commits intomainfrom
feature/event

Conversation

@naooung
Copy link
Member

@naooung naooung commented Dec 12, 2025

✨ 새로운 기능

  • 이벤트(Event) 생성, 상세 조회, 예약, 그룹 조회 기능을 구현했습니다.
  • 사용자는
    • 다가오는 / 예정된 / 종료된 이벤트를 각각 1개씩 확인할 수 있습니다.
    • 이벤트 상세 화면에서 예약 여부, 최근 업로드된 사진 4장, 전체 업로드된 옷 수를 확인할 수 있습니다.
    • 이벤트 예약 시 사용자 계정과 연동된 예약 정보를 생성할 수 있습니다.

🛠 개발 상세

  • 이벤트 그룹 조회

    • 기준 날짜(LocalDate.now())를 기반으로 이벤트를 분류
      • 다가오는 이벤트: D-day가 가장 가까운 이벤트 1개
      • 예정된 이벤트: 다가오는 이벤트 다음 순서 이벤트 1개
      • 종료된 이벤트: 가장 최근 종료된 이벤트 1개
    • 단건 반환을 위해 toGroupResponseSingle 매퍼 메서드 추가
  • 이벤트 상세 조회

    • 이벤트 기본 정보 + 사용자 예약 여부(isReserved) 반환
    • EventReservationImage 기준으로
      • 최근 업로드된 이미지 4장 조회
      • 전체 업로드된 이미지 수에서 4를 제외한 값 계산하여 반환
    • 로그인하지 않은 사용자의 경우에도 조회 가능하도록 사용자 조회 예외 처리
  • 이벤트 예약

    • Event–User 기반 예약(EventReservation) 생성
    • 예약 인원은 1명 단위 증가 로직으로 단순화
  • 구조 개선

    • Entity 생성 로직을 Mapper로 분리하여 Service 책임 최소화
    • Event / Reservation / Image 간 책임을 명확히 분리

🧪 테스트 방법

  • 이벤트 생성 API 정상 동작 확인 (multipart + JSON)
  • 이벤트 그룹 조회 시 다가오는 / 예정된 / 종료된 이벤트가 각각 1개씩 반환되는지 확인
  • 이벤트 상세 조회 시
    • 최근 이미지 4장 정상 반환
    • 전체 업로드 수 계산 값 정상 반영 확인
  • 로그인 / 비로그인 사용자 모두 상세 조회 가능 여부 확인
  • 동일 사용자의 이벤트 다중 예약 가능 여부 확인

🔗 관련 문서 / 이슈

Summary by CodeRabbit

  • New Features

    • 이벤트 생성·수정·삭제 기능 추가
    • 예정/종료 이벤트 목록 및 이벤트 그룹(카드/요약) 제공
    • 이벤트 상세 조회 및 예약 기능 추가(예약자 이미지 업로드 포함)
    • 예약 이미지 갤러리 제공(최근 4장 조회·전체 조회)
    • WebP 이미지 업로드 지원으로 이미지 처리 최적화
  • Chores

    • 관리자 경로 매칭 규칙 개선(/admin 및 하위 경로 포함)

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

@naooung naooung self-assigned this Dec 12, 2025
@naooung naooung added the ✨ feature 새로운 기능 요청 label Dec 12, 2025
@naooung naooung linked an issue Dec 12, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Walkthrough

이 PR은 이벤트 도메인(엔티티·리포지토리·DTO·매퍼·서비스·컨트롤러·예외)을 신규로 추가하고 S3 업로드에 WebP 변환·업로드 기능 및 관련 S3 경로를 도입하며 보안 설정의 admin 경로 정규식을 확장합니다. 빌드 의존성에 Scrimage(WebP) 라이브러리를 추가했습니다.

Changes

Cohort / File(s) Summary
빌드
build.gradle
이미지 WebP 변환 업로드를 위한 Scrimage 의존성 추가: com.sksamuel.scrimage:scrimage-core:4.3.5, com.sksamuel.scrimage:scrimage-webp:4.3.5.
Event 엔티티 및 저장소
src/main/java/com/sku/refit/domain/event/entity/Event.java, src/main/java/com/sku/refit/domain/event/entity/EventReservation.java, src/main/java/com/sku/refit/domain/event/entity/EventReservationImage.java, src/main/java/com/sku/refit/domain/event/repository/EventRepository.java, .../EventReservationRepository.java, .../EventReservationImageRepository.java
Event, EventReservation, EventReservationImage JPA 엔티티 추가 및 날짜 기반 조회·중복체크·최근 이미지 조회 등 리포지토리 메서드 추가(유니크 제약 포함).
Event DTO (요청/응답)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java, src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java
이벤트 생성/예약 요청 DTO와 상세/카드/심플/그룹/이미지/예약 응답 DTO 정의(검증 어노테이션 포함).
Event 매퍼
src/main/java/com/sku/refit/domain/event/mapper/EventMapper.java
엔티티 ↔ DTO 변환 로직 추가(리스트 매핑, D-day 계산, recent images 집계 등).
Event 서비스 계층
src/main/java/com/sku/refit/domain/event/service/EventService.java, .../EventServiceImpl.java
이벤트 CRUD, 예약 로직, 이미지 업로드(WebP)·삭제, S3 연동, 트랜잭션 경계 및 예외 처리 포함한 서비스 인터페이스 및 구현 추가.
Event 컨트롤러
src/main/java/com/sku/refit/domain/event/controller/EventController.java, .../EventControllerImpl.java
REST 엔드포인트(관리자·공개) 및 구현 추가 — multipart 파일 업로드(썸네일·클로스 이미지) 지원.
Event 예외 처리
src/main/java/com/sku/refit/domain/event/exception/EventErrorCode.java
이벤트 관련 에러코드 열거형 추가(상태·메시지 매핑).
S3 및 글로벌 설정
src/main/java/com/sku/refit/global/config/S3Config.java, .../s3/entity/PathName.java, .../s3/service/S3Service.java, .../s3/service/S3ServiceImpl.java, src/main/java/com/sku/refit/global/config/SecurityConfig.java
S3Config에 eventPath 추가, PathName에 EVENT 추가, S3Service에 uploadFileAsWebp 시그니처 추가, S3ServiceImpl에 WebP 변환(convertToWebp), WEBP_QUALITY 상수, 이벤트 경로 처리 및 WebP 업로드 흐름 추가. SecurityConfig의 admin 경로 정규식을 ".*/admin($
리소스/기타
src/main/resources
서브모듈 참조 해시 업데이트(코드 변경 없음).

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant EventCtrl as EventController
    participant EventSvc as EventService
    participant S3Svc as S3Service
    participant EventRepo as EventRepository
    participant S3 as AWS_S3

    Client->>EventCtrl: POST /events (EventInfoRequest + thumbnail)
    EventCtrl->>EventSvc: createEvent(request, thumbnail)
    EventSvc->>S3Svc: uploadFileAsWebp(EVENT, thumbnail)
    S3Svc->>S3Svc: convertToWebp(file) (encode quality=90)
    S3Svc->>S3: PUT .webp object
    S3-->>S3Svc: object URL
    S3Svc-->>EventSvc: thumbnailUrl
    EventSvc->>EventRepo: save(Event)
    EventRepo-->>EventSvc: saved Event
    EventSvc-->>EventCtrl: EventDetailResponse
    EventCtrl-->>Client: 200 OK + response
Loading
sequenceDiagram
    actor User
    participant EventCtrl as EventController
    participant EventSvc as EventService
    participant EventResRepo as EventReservationRepository
    participant S3Svc as S3Service
    participant UserSvc as UserService
    participant EventRepo as EventRepository

    User->>EventCtrl: POST /events/{id}/reserve (EventRsvRequest + images)
    EventCtrl->>EventSvc: reserveEvent(eventId, request, images)
    EventSvc->>EventResRepo: existsByEventIdAndUserId(eventId, userId)
    EventResRepo-->>EventSvc: false
    EventSvc->>EventRepo: findById(eventId)
    EventRepo-->>EventSvc: Event
    EventSvc->>UserSvc: getCurrentUser()
    UserSvc-->>EventSvc: User
    loop for each image
        EventSvc->>S3Svc: uploadFileAsWebp(EVENT, image)
        S3Svc-->>EventSvc: imageUrl
    end
    EventSvc->>EventResRepo: save(reservation + images)
    EventSvc->>EventRepo: increaseReservedCount(event)
    EventSvc-->>EventCtrl: EventReservationResponse
    EventCtrl-->>User: 200 OK + response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • 주의 깊게 살펴볼 파일/포인트:
    • S3ServiceImpl: WebP 변환(스트림/이미지 디코딩·인코딩) 예외 처리와 리소스 해제
    • EventServiceImpl: 트랜잭션 경계, 썸네일 교체 시 기존 객체 삭제/복구 로직, 예약 일관성(uk_event_user) 처리
    • EventMapper: 날짜(D-day) 계산 및 null 안전성
    • 리포지토리 쿼리(페이징/정렬)의 날짜 조건 일관성
    • SecurityConfig의 정규식 변경이 의도된 엔드포인트 범위를 정확히 포함하는지

Possibly related PRs

  • Feature/community #2 — S3 경로 설정 및 PathName 관련 변경과 중복되는 S3 경로/enum 업데이트 작업이 있음

Suggested labels

🟠 priority: medium

Poem

🐰 새 행사가 도착했네,
이미지는 WebP로 반짝이고,
예약서가 폴더에 쌓이네,
S3에 안전히 넣고 또 뛰어다녀요,
토끼가 통통 축하해요! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.85% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive PR 제목 'Feature/event'는 변경의 주요 내용(이벤트 기능 구현)을 일반적으로 나타내지만, 구체성이 부족합니다. 제목을 'Add event creation, detail, reservation and grouping APIs' 같이 더 구체적으로 수정하여 변경의 범위를 명확히 하세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 기능, 개발 상세, 테스트 방법, 관련 이슈를 모두 포함하고 있습니다.
✨ 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 feature/event

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

🧹 Nitpick comments (9)
src/main/java/com/sku/refit/domain/event/entity/Event.java (2)

64-66: 불필요한 방어적 null 체크

totalReservedCount@Column(nullable = false)이고 @Builder.Default = 0으로 초기화되므로, Line 65의 null 체크는 불필요합니다.

다음과 같이 단순화할 수 있습니다:

 public void increaseReservedCount() {
-  this.totalReservedCount = (this.totalReservedCount == null ? 0 : this.totalReservedCount) + 1;
+  this.totalReservedCount += 1;
 }

51-58: 예약 취소 시나리오 고려

increaseReservedCount() 메서드만 제공되고 감소 메서드는 없습니다. 예약 취소 기능이 추가될 경우를 대비해 decreaseReservedCount() 메서드 추가를 고려해보세요.

필요하다면 예약 취소 관련 구현을 도와드릴 수 있습니다. 새 이슈를 생성하시겠습니까?

src/main/java/com/sku/refit/domain/event/entity/EventReservationImage.java (1)

24-42: 엔티티 구조가 적절합니다.

JPA 엔티티 설계가 올바르며, FetchType.LAZY 사용과 nullable = false 제약 조건이 적절합니다.

imageUrl 컬럼에 길이 제약을 추가하는 것을 고려해 보세요. 매우 긴 URL이 저장될 경우 데이터베이스 성능에 영향을 줄 수 있습니다:

-  @Column(nullable = false)
+  @Column(nullable = false, length = 512)
   private String imageUrl;
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1)

52-54: 전화번호 형식 검증 추가 권장

@NotBlank만으로는 전화번호 형식을 검증하지 않습니다. @Pattern을 사용하여 전화번호 형식을 검증하는 것을 고려하세요.

    @NotBlank(message = "연락처는 필수입니다.")
+   @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "올바른 전화번호 형식이 아닙니다.")
    @Schema(description = "연락처", example = "010-1234-5678")
    private String phone;
src/main/java/com/sku/refit/domain/event/service/EventService.java (1)

10-11: 와일드카드 임포트 사용

와일드카드 임포트(.*)는 어떤 클래스가 사용되는지 명확하지 않아 코드 가독성에 영향을 줄 수 있습니다. 명시적 임포트를 권장합니다.

-import com.sku.refit.domain.event.dto.request.EventRequest.*;
-import com.sku.refit.domain.event.dto.response.EventResponse.*;
+import com.sku.refit.domain.event.dto.request.EventRequest.EventInfoRequest;
+import com.sku.refit.domain.event.dto.request.EventRequest.EventRsvRequest;
+import com.sku.refit.domain.event.dto.response.EventResponse.EventCardResponse;
+import com.sku.refit.domain.event.dto.response.EventResponse.EventDetailResponse;
+import com.sku.refit.domain.event.dto.response.EventResponse.EventGroupResponse;
+import com.sku.refit.domain.event.dto.response.EventResponse.EventImageResponse;
+import com.sku.refit.domain.event.dto.response.EventResponse.EventReservationResponse;
+import com.sku.refit.domain.event.dto.response.EventResponse.EventSimpleResponse;
src/main/java/com/sku/refit/global/s3/service/S3ServiceImpl.java (2)

249-252: 불필요한 null 체크

ImmutableImage.loader().fromStream()은 스트림 읽기에 실패하면 예외를 던지므로, 성공적으로 반환된 image에 대한 null 체크는 불필요합니다.

-      if (image == null) {
-        log.warn("이미지 디코딩 실패 - originalFilename: {}", file.getOriginalFilename());
-        throw new CustomException(S3ErrorStatus.FILE_SERVER_ERROR);
-      }

235-279: convertToWebp 메서드 구조 단순화 권장

중첩된 try-catch 블록을 단일 try-catch로 통합하면 가독성이 향상됩니다.

  private byte[] convertToWebp(MultipartFile file) {
-
-    try {
-      ImmutableImage image;
-      try {
-        image = ImmutableImage.loader().fromStream(file.getInputStream());
-      } catch (IOException e) {
-        log.error(
-            "이미지 디코딩 오류 - originalFilename: {}, message: {}",
-            file.getOriginalFilename(),
-            e.getMessage());
-        throw new CustomException(S3ErrorStatus.FILE_SERVER_ERROR);
-      }
-
-      if (image == null) {
-        log.warn("이미지 디코딩 실패 - originalFilename: {}", file.getOriginalFilename());
-        throw new CustomException(S3ErrorStatus.FILE_SERVER_ERROR);
-      }
-
-      WebpWriter writer = WebpWriter.DEFAULT.withQ(WEBP_QUALITY);
-
-      ByteArrayOutputStream baos = new ByteArrayOutputStream();
-      try {
-        image.forWriter(writer).write(baos);
-      } catch (IOException e) {
-        log.error(
-            "WebP 변환 중 IO 오류 - originalFilename: {}, message: {}",
-            file.getOriginalFilename(),
-            e.getMessage());
-        throw new CustomException(S3ErrorStatus.FILE_SERVER_ERROR);
-      }
-
-      return baos.toByteArray();
-
-    } catch (CustomException e) {
-      throw e;
-
-    } catch (Exception e) {
-      log.error(
-          "WebP 변환 중 예기치 않은 오류 - originalFilename: {}, message: {}",
-          file.getOriginalFilename(),
-          e.getMessage());
-      throw new CustomException(S3ErrorStatus.FILE_SERVER_ERROR);
-    }
+    try {
+      ImmutableImage image = ImmutableImage.loader().fromStream(file.getInputStream());
+      WebpWriter writer = WebpWriter.DEFAULT.withQ(WEBP_QUALITY);
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      image.forWriter(writer).write(baos);
+      return baos.toByteArray();
+    } catch (IOException e) {
+      log.error("WebP 변환 중 오류 - originalFilename: {}, message: {}",
+          file.getOriginalFilename(), e.getMessage());
+      throw new CustomException(S3ErrorStatus.FILE_SERVER_ERROR);
+    }
  }
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (2)

189-231: LocalDate.now()는 서버 타임존에 따라 이벤트 분류가 흔들릴 수 있습니다.

비즈니스 기준(예: Asia/Seoul) 타임존이 따로 있다면 Clock/ZoneId 기반으로 today를 계산하도록 하는 게 안전합니다.


245-255: 익명 사용자 처리에서 예외를 삼키는 대신, 최소 로그/명시적 값이 있으면 좋습니다.

지금은 getCurrentUser() 예외를 무시하고 isReserved = null을 반환합니다. API 계약상 익명은 false가 더 자연스럽다면 기본값을 false로 두고, 예외는 debug 수준이라도 남기는 편이 운영에 유리합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69fc9bc and ead9699.

📒 Files selected for processing (21)
  • build.gradle (1 hunks)
  • src/main/java/com/sku/refit/domain/event/controller/EventController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/controller/EventControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/entity/Event.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/entity/EventReservation.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/entity/EventReservationImage.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/exception/EventErrorCode.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/mapper/EventMapper.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/repository/EventRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/repository/EventReservationImageRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/repository/EventReservationRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/service/EventService.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1 hunks)
  • src/main/java/com/sku/refit/global/config/S3Config.java (1 hunks)
  • src/main/java/com/sku/refit/global/config/SecurityConfig.java (1 hunks)
  • src/main/java/com/sku/refit/global/s3/entity/PathName.java (1 hunks)
  • src/main/java/com/sku/refit/global/s3/service/S3Service.java (1 hunks)
  • src/main/java/com/sku/refit/global/s3/service/S3ServiceImpl.java (4 hunks)
  • src/main/resources (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/main/java/com/sku/refit/domain/event/entity/EventReservationImage.java (3)
src/main/java/com/sku/refit/domain/event/entity/Event.java (1)
  • Entity (18-67)
src/main/java/com/sku/refit/domain/event/entity/EventReservation.java (1)
  • Entity (13-54)
src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (6)
  • Getter (15-58)
  • Getter (60-70)
  • Getter (72-85)
  • Getter (89-114)
  • Getter (116-135)
  • Getter (137-150)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1)
src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (7)
  • Schema (12-151)
  • Getter (15-58)
  • Getter (60-70)
  • Getter (72-85)
  • Getter (89-114)
  • Getter (116-135)
  • Getter (137-150)
src/main/java/com/sku/refit/domain/event/service/EventService.java (1)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1)
  • EventRequest (13-68)
src/main/java/com/sku/refit/domain/event/entity/EventReservation.java (3)
src/main/java/com/sku/refit/domain/event/entity/Event.java (1)
  • Entity (18-67)
src/main/java/com/sku/refit/domain/event/entity/EventReservationImage.java (1)
  • Entity (24-42)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (2)
  • Getter (15-39)
  • Getter (41-67)
src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (1)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (2)
  • Getter (15-39)
  • Getter (41-67)
src/main/java/com/sku/refit/domain/event/entity/Event.java (4)
src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (6)
  • Getter (15-58)
  • Getter (60-70)
  • Getter (72-85)
  • Getter (89-114)
  • Getter (116-135)
  • Getter (137-150)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (2)
  • Getter (15-39)
  • Getter (41-67)
src/main/java/com/sku/refit/domain/event/entity/EventReservation.java (1)
  • Entity (13-54)
src/main/java/com/sku/refit/domain/event/entity/EventReservationImage.java (1)
  • Entity (24-42)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1)
  • EventRequest (13-68)
src/main/java/com/sku/refit/domain/event/controller/EventController.java (1)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1)
  • EventRequest (13-68)
⏰ 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: Build
🔇 Additional comments (18)
src/main/resources (1)

1-1: 서브모듈 커밋 포인터 업데이트의 실제 변경 내용 확인 필요

Line 1의 gitlink 포인터 변경(547102251cecc07b04c80516470cd71d4f4a2145d23b7c2b63504e84ea800b139b7e9235d4bf045f)만으로는 refit-be-config 서브모듈 내 기능/보안/설정 변경 사항을 평가할 수 없습니다. 다음을 확인해 주세요:

  • 서브모듈 저장소에서 두 커밋 간의 실제 변경 사항(특히 리소스/설정/보안 정책) 확인
  • PR 설명에 서브모듈 변경 내역과 비교 링크(git diff <old-sha> <new-sha>) 추가
  • 새 커밋이 서브모듈 저장소에 실제로 존재하는지 검증
  • CI에서 서브모듈을 올바르게 페치하고 검증하는지 확인 (서명된 커밋/태그 선호)
src/main/java/com/sku/refit/domain/event/exception/EventErrorCode.java (2)

13-41: 에러 코드 구조는 잘 설계됨

EVENT_ALREADY_RESERVED 이슈를 제외하면, 에러 코드들이 잘 정의되어 있습니다. HTTP 상태 코드 매핑이 적절하고, 에러 메시지가 명확합니다.


31-31: 삭제 또는 수정 필요 없음

현재 코드는 일관성 있게 구현되어 있습니다. EventReservation 엔티티는 여전히 event_iduser_id에 대한 @UniqueConstraint를 유지하고 있으며, EventServiceImplreserveEvent 메서드 (line 300-301)에서 existsByEventIdAndUserId() 체크를 통해 명시적으로 중복 예약을 방지합니다. EVENT_ALREADY_RESERVED 에러 코드는 이 중복 예약 방지 로직에서 활발히 사용되고 있어 완전히 정당하고 필요한 코드입니다.

Likely an incorrect or invalid review comment.

src/main/java/com/sku/refit/domain/event/entity/Event.java (1)

47-49: 예약 카운트 초기화 적절함

totalReservedCount@Builder.Default = 0nullable = false로 설정하여 null 값을 방지하고 있습니다. 좋은 설계입니다.

src/main/java/com/sku/refit/domain/event/repository/EventRepository.java (1)

14-22: LGTM! 리포지토리 쿼리 메서드 잘 설계됨

Spring Data JPA 네이밍 컨벤션을 잘 따르고 있으며, 날짜 기반 필터링과 정렬이 명확합니다. Pageable 버전과 비Pageable 버전을 모두 제공하여 유연성이 확보되었습니다.

src/main/java/com/sku/refit/global/s3/entity/PathName.java (1)

15-16: LGTM! Enum 상수 추가 적절함

기존 패턴을 잘 따르고 있으며, Swagger 문서화도 포함되어 있습니다.

src/main/java/com/sku/refit/global/config/S3Config.java (1)

48-49: LGTM! S3 경로 설정 추가 적절함

기존 경로 필드들(profileImagePath, postPath, clothPath)과 일관된 패턴으로 추가되었습니다.

src/main/java/com/sku/refit/global/s3/service/S3Service.java (1)

42-43: WebP 업로드 메서드 구현 확인 완료

인터페이스에 추가된 uploadFileAsWebp 메서드의 구현을 확인했습니다. 구현체에서 WebP 변환이 적절히 처리되고 있습니다:

  • WebP 품질 설정: 90 (적절한 수준)
  • scrimage 라이브러리 사용: ImmutableImage와 WebpWriter 활용
  • 포괄적인 에러 처리: 이미지 디코딩 오류, IO 오류 모두 처리
  • 적절한 로깅 및 리소스 관리

구현이 완전하고 적절합니다.

src/main/java/com/sku/refit/global/config/SecurityConfig.java (1)

128-128: 관리자 경로 정규식 개선 확인

정규식이 .*/admin/.*에서 .*/admin($|/.*)으로 변경되어 /admin 경로 자체도 보호됩니다. 이는 올바른 개선으로, EventController의 모든 관리자 엔드포인트(@PostMapping("/admin"), @PutMapping("/admin/{id}"), @DeleteMapping("/admin/{id}"))가 이 정규식에 의해 올바르게 보호됩니다.

src/main/java/com/sku/refit/domain/event/repository/EventReservationRepository.java (1)

12-17: LGTM!

Spring Data JPA 쿼리 메서드 명명 규칙을 올바르게 따르고 있습니다. existsByEventIdAndUserId는 예약 상태 확인에 효율적입니다.

src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (2)

15-58: DTO 구조가 잘 설계되었습니다.

응답 DTO들이 일관된 패턴을 따르고 있으며, Swagger 문서화가 잘 되어 있습니다. isReservedBoolean으로 정의하여 비로그인 사용자에게 null을 반환하는 설계가 적절합니다.


87-151: 리스트용 DTO 구조 적절함

EventGroupResponse가 다른 DTO들을 조합하여 사용하는 설계가 깔끔합니다. 각 행사 유형별로 적절한 응답 형태(카드형, 간단형)를 구분한 것이 좋습니다.

src/main/java/com/sku/refit/domain/event/service/EventService.java (1)

26-160: 서비스 인터페이스 설계가 적절합니다.

관리자 기능, 목록 조회, 상세 조회, 예약 기능이 명확하게 분리되어 있으며, Javadoc 문서화가 잘 되어 있습니다.

src/main/java/com/sku/refit/global/s3/service/S3ServiceImpl.java (1)

208-233: WebP 업로드 구현이 적절합니다.

try-with-resources를 사용한 스트림 관리와 일관된 오류 처리가 잘 되어 있습니다.

src/main/java/com/sku/refit/domain/event/controller/EventController.java (2)

24-57: /admin 엔드포인트가 실제로 관리자 인가로 보호되는지 확인이 필요합니다.

컨트롤러 계약만 보면 공개 호출도 가능해 보이므로, Spring Security 설정에서 /api/events/admin/**가 ADMIN 전용으로 제한되는지(또는 별도 필터/인가 로직이 있는지) 꼭 확인해 주세요.


95-100: clothImageList가 optional이면, 파일 개수/총 용량 제한도 서버 단에서 보장되는지 확인 권장.

src/main/java/com/sku/refit/domain/event/mapper/EventMapper.java (2)

51-53: EventImageResponse.orderid를 넣는 게 계약상 의도인지 확인 필요.

정렬 키를 내려주려는 거면 OK인데, “1,2,3…” 표시용 순번이라면 클라이언트에서 기대와 다를 수 있어 createdAt 기반 정렬값/혹은 응답 생성 시 index를 쓰는 편이 명확합니다.


59-70: dday 계산은 upcoming 전제면 적절합니다.

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

♻️ Duplicate comments (2)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (2)

135-186: 이벤트 삭제도 “S3 삭제 ↔ DB 롤백” 정합성 깨질 수 있음(부분 성공/누수/유실)
S3 삭제 도중/이후 예외로 DB가 롤백되면(또는 반대로) 파일 유실/누수가 생길 수 있습니다. 최소한 (1) 커밋 이후 S3 삭제로 이동, (2) 실패 시 best-effort로 계속 진행 후 로그/재시도(보상 작업) 등 전략을 정하는 게 안전합니다.


301-304: PR 목표(동일 유저 중복 예약 허용)와 구현이 불일치 — 중복 예약 차단 로직 제거 필요
현재 existsByEventIdAndUserId로 중복 예약을 막고 있어, PR 설명(“같은 유저 여러 번 예약 가능”)과 정면으로 충돌합니다. (DB 유니크/검증/에러코드 포함) 정책에 맞게 통일해 주세요.

-    if (eventReservationRepository.existsByEventIdAndUserId(eventId, user.getId())) {
-      throw new CustomException(EventErrorCode.EVENT_ALREADY_RESERVED);
-    }
🧹 Nitpick comments (2)
src/main/java/com/sku/refit/domain/event/repository/EventReservationImageRepository.java (2)

15-19: countBy... 반환 타입은 long 권장 + “최근 4장” 정렬 기준 재검토 필요
현재 int는 대량 데이터에서 오버플로우 가능성이 있고, OrderByIdDesc는 “최신 업로드”를 id에 의존합니다(이벤트 이미지에 createdAt이 있다면 그 기준이 더 안전).

-  int countByReservation_Event_Id(Long eventId);
+  long countByReservation_Event_Id(Long eventId);

19-19: findAll...는 데이터 증가 시 메모리/응답시간 리스크(페이지네이션 고려)
지금 요구사항이 “전체 조회”라면 OK지만, 이미지가 늘어나면 한 번에 모두 로드하는 API는 병목이 될 수 있어 Pageable/cursor 기반 확장 여지를 남겨두는 편이 안전합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead9699 and 178b828.

📒 Files selected for processing (3)
  • src/main/java/com/sku/refit/domain/event/controller/EventControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/repository/EventReservationImageRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/sku/refit/domain/event/controller/EventControllerImpl.java
🧰 Additional context used
🪛 GitHub Actions: CI
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java

[error] 81-95: Spotless Java formatting violations detected in EventServiceImpl.java. Run './gradlew :spotlessApply' to fix.

🔇 Additional comments (1)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1)

254-268: isReserved가 익명일 때 null로 내려가는 계약은 프론트/클라이언트와 합의 필요
익명 시 null이면 UI에서 삼상(unknown/false/true) 처리 필요합니다. 요구사항이 “예약 여부”면 보통 false가 더 일관적이라, API 스펙 상 null을 의도한 게 맞는지 확인이 필요합니다.

@naooung naooung merged commit a2139d0 into main Dec 12, 2025
2 of 3 checks passed
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: 2

♻️ Duplicate comments (5)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (5)

114-135: 썸네일 삭제는 트랜잭션 커밋 이후로 미루는 게 안전합니다
현재는 새 썸네일 업로드/엔티티 업데이트 후(Line 117-126) 즉시 기존 썸네일을 삭제(Line 127-134)합니다. 이후 트랜잭션이 롤백되면 DB는 예전 URL을 유지하지만 S3의 예전 썸네일은 이미 삭제될 수 있어 “유실”이 발생합니다(커밋 이후 afterCommit에서 best-effort 삭제 권장).


137-188: deleteEvent()는 S3 부분 삭제/DB 롤백 시 정합성 깨질 수 있어 “커밋 이후 정리” 또는 “best-effort 후 DB 정리”가 필요합니다
현재는 S3 삭제를 선행하고(Line 147-173) 중간 실패 시 예외를 던져 트랜잭션이 롤백되므로, “S3는 일부 삭제됐는데 DB는 그대로” 상태가 가능합니다. 일반적으로는 (1) 삭제 대상 key들을 수집 → (2) DB 삭제 커밋 → (3) afterCommit에서 S3 best-effort 삭제가 누락/깨짐에 더 안전합니다.


242-272: 익명 처리: 모든 예외를 삼키지 말고 “인증 예외만” 무시하세요
현재 userService.getCurrentUser()에서 발생하는 예외를 전부 무시(Line 251-255)하면, 인증 이슈가 아닌 실제 장애(DB/네트워크/버그)도 조용히 숨겨집니다. 예상한 인증/인가 예외만 catch 하고 나머지는 로깅+전파가 안전합니다.


292-307: 예약 정책이 PR 목표와 불일치: “중복 예약 허용”이면 중복 체크/유니크 제약 제거 필요
PR objectives는 “같은 유저가 여러 번 예약 가능”인데, 현재는 existsByEventIdAndUserId로 중복을 차단(Line 304-306)합니다. 정책대로라면 이 체크 제거 + (event_id, user_id) 유니크 제약(엔티티/마이그레이션) 정합성도 같이 맞춰야 합니다.


312-326: 예약 이미지 업로드는 롤백 시 S3 고아 파일이 남습니다(보상 삭제/afterCompletion 필요)
루프 중 일부 업로드 성공 후 예외가 나면(Line 313-324) DB는 롤백되지만 S3는 남습니다. 업로드된 key를 누적해 예외 시 best-effort 삭제하거나 트랜잭션 동기화로 롤백 시 정리하세요.

🧹 Nitpick comments (1)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1)

212-236: LocalDate.now()는 테스트/타임존 민감 → Clock 주입 권장
그룹 분류 기준이 서버 로컬 타임존에 고정되어 있고(Line 214-224), 단위 테스트도 어려워집니다. Clock(또는 “오늘” 제공자)을 주입해 결정 가능하게 만드는 걸 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 178b828 and 8a55e9a.

📒 Files selected for processing (2)
  • build.gradle (1 hunks)
  • src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1)
  • EventRequest (13-68)
⏰ 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: Build
🔇 Additional comments (4)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (4)

82-112: updateEvent()에서 도메인 업데이트/썸네일 교체 흐름은 명확합니다
event.update(...) 후 썸네일이 있으면 교체하는 구조는 이해하기 좋습니다.


194-211: 목록 조회(getUpcomingEvents, getEndedEvents)는 단순/명확합니다
Mapper에서 today 기반 D-day 계산을 맡긴 구조도 응집도가 좋습니다. (대량 데이터라면 페이징 고려 정도)


274-286: getEventAllReservationImages()는 존재 확인 후 조회하는 흐름이 적절합니다
이벤트 존재 검증 후 이미지 조회로 일관성이 있습니다.


328-331: increaseReservedCount() 위치/의미는 “예약 1회당 +1”로 명확합니다
현재 위치라면 이미지 업로드 실패 시 증가가 반영되지 않는 점도(트랜잭션 롤백) 의도와 맞습니다.

@naooung naooung deleted the feature/event branch December 12, 2025 21:54
@coderabbitai coderabbitai bot mentioned this pull request Dec 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feature 새로운 기능 요청

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨Feat: 행사 기능 개발

1 participant