Conversation
Walkthrough이 PR은 이벤트 도메인(엔티티·리포지토리·DTO·매퍼·서비스·컨트롤러·예외)을 신규로 추가하고 S3 업로드에 WebP 변환·업로드 기능 및 관련 S3 경로를 도입하며 보안 설정의 admin 경로 정규식을 확장합니다. 빌드 의존성에 Scrimage(WebP) 라이브러리를 추가했습니다. Changes
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 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 포인터 변경(
547102251cecc07b04c80516470cd71d4f4a2145→d23b7c2b63504e84ea800b139b7e9235d4bf045f)만으로는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_id와user_id에 대한@UniqueConstraint를 유지하고 있으며,EventServiceImpl의reserveEvent메서드 (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 = 0과nullable = 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 문서화가 잘 되어 있습니다.
isReserved를Boolean으로 정의하여 비로그인 사용자에게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.order에id를 넣는 게 계약상 의도인지 확인 필요.정렬 키를 내려주려는 거면 OK인데, “1,2,3…” 표시용 순번이라면 클라이언트에서 기대와 다를 수 있어
createdAt기반 정렬값/혹은 응답 생성 시 index를 쓰는 편이 명확합니다.
59-70:dday계산은 upcoming 전제면 적절합니다.
src/main/java/com/sku/refit/domain/event/controller/EventControllerImpl.java
Show resolved
Hide resolved
src/main/java/com/sku/refit/domain/event/repository/EventReservationImageRepository.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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
📒 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을 의도한 게 맞는지 확인이 필요합니다.
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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
📒 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”로 명확합니다
현재 위치라면 이미지 업로드 실패 시 증가가 반영되지 않는 점도(트랜잭션 롤백) 의도와 맞습니다.
✨ 새로운 기능
🛠 개발 상세
이벤트 그룹 조회
toGroupResponseSingle매퍼 메서드 추가이벤트 상세 조회
EventReservationImage기준으로이벤트 예약
구조 개선
🧪 테스트 방법
🔗 관련 문서 / 이슈
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.