Skip to content

Conversation

@naooung
Copy link
Member

@naooung naooung commented Dec 14, 2025

✨ 새로운 기능

  • 관리자 행사 관리 기능 추가
    • 행사 생성 / 수정 / 삭제 API
    • 관리자 전용 행사 리스트 조회(페이지네이션, 상태 필터, 검색)
  • 행사 상태 기반 조회 및 정렬
    • 진행중(ONGOING) → 예정(UPCOMING) → 종료(ENDED) 우선 정렬

🛠 개발 상세

  • Admin 전용 행사 CRUD API
    • POST /api/events/admin : 행사 생성 (multipart, 썸네일 포함)
    • PUT /api/events/admin/{id} : 행사 수정
    • DELETE /api/events/admin/{id} :행사 및 관련 이미지 전체 삭제
  • 관리자 행사 리스트 조회
    • GET /api/events/admin
    • 페이지네이션 기반 조회
    • 상태 필터: UPCOMING / ONGOING / ENDED
    • 검색어(q): 행사명 / 장소 부분 일치 검색
  • 행사 상태 판정 정책
    • UPCOMING : startDate > today
    • ONGOING : startDate <= today <= endDate
    • ENDED : endDate < today
  • 정렬 정책
    • 전체 조회 시: ONGOING → UPCOMING → ENDED
    • 상태별 조회 시
      • ONGOING: startDate DESC
      • UPCOMING: startDate ASC
      • ENDED: endDate DESC
  • JPQL + CASE WHEN 정렬을 사용하여 DB 레벨에서 정렬 처리

🔗 관련 문서 / 이슈

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 행사 일정을 시작일과 종료일로 구분하여 관리
    • 행사 예약 정원 관리 기능 추가
    • 행사 상태별(예정, 진행중, 완료) 필터링 및 검색 기능
    • 행사 목록 페이지네이션 지원
  • 개선 사항

    • 내 활동 페이지의 페이지 기반 네비게이션으로 변경
    • API 문서 및 파라미터 설명 강화

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

@naooung naooung self-assigned this Dec 14, 2025
@naooung naooung added ✨ feature 새로운 기능 요청 ♻️ refactor 리팩토링 작업 🟠 priority: medium 중간 우선순위 labels Dec 14, 2025
@naooung naooung linked an issue Dec 14, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Walkthrough

이벤트 도메인의 날짜 필드를 단일 date에서 startDate/endDate로 분리하고, capacity 필드를 추가했습니다. 또한 EventStatus 열거형을 도입하고, 이벤트 목록 조회에 페이지네이션과 상태 필터링을 지원하는 새로운 엔드포인트와 쿼리 메서드를 추가했습니다. MyPage 영역도 커서 기반 페이지네이션에서 페이지 기반 페이지네이션으로 마이그레이션했습니다.

Changes

Cohort / File(s) 변경 요약
이벤트 컨트롤러
src/main/java/com/sku/refit/domain/event/controller/EventController.java, EventControllerImpl.java
와일드카드 임포트를 명시적 임포트로 변경. 관리자 목록 조회(getEvents), 예정된 이벤트(getUpcomingEvents), 종료된 이벤트(getEndedEvents), 이벤트 그룹화(getEventGroups) 등 새로운 엔드포인트 추가. 기존 엔드포인트에 상세한 매개변수 설명 및 @Parameter 주석 추가.
이벤트 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
EventInfoRequest에서 date 필드를 startDate/endDate로 분리하고 capacity 필드 추가. EventDetailResponse, EventCardResponse, EventSimpleResponse 등의 응답 DTO도 date 필드 변경. 새로운 중첩 DTO EventPagedResponse와 EventListItem 추가(페이지네이션 지원).
이벤트 엔티티 및 상태
src/main/java/com/sku/refit/domain/event/entity/Event.java, EventStatus.java
Event 엔티티에서 date를 startDate/endDate로 분리, capacity 필드 추가, update() 메서드 시그니처 변경, isFull() 메서드 추가. 새로운 EventStatus 열거형 도입(UPCOMING, ONGOING, ENDED).
에러 코드 및 에러 처리
src/main/java/com/sku/refit/domain/event/exception/EventErrorCode.java
EVENT_INVALID_DATE_RANGE, EVENT_INVALID_CAPACITY, EVENT_CAPACITY_EXCEEDED, EVENT_RESERVATION_IMAGES_DELETE_FAILED, EVENT_LIST_FETCH_FAILED 등 새로운 에러 코드 추가. 기존 에러 코드의 코드 값 재편성.
이벤트 매퍼 및 저장소
src/main/java/com/sku/refit/domain/event/mapper/EventMapper.java, src/main/java/com/sku/refit/domain/event/repository/EventRepository.java
EventMapper에서 date 필드를 startDate/endDate로 처리, toPagedResponse() 메서드 추가(페이지네이션 지원), 상태 결정 로직(resolveStatus) 추가. EventRepository에서 findByDateGreaterThanEqual 등의 메서드를 findByStartDateGreaterThanEqual로 변경, 상태별 정렬 조회 메서드 추가(@Query 사용).
이벤트 서비스
src/main/java/com/sku/refit/domain/event/service/EventService.java, EventServiceImpl.java
페이지네이션과 상태 필터링을 지원하는 getEvents(page, size, status, q) 메서드 추가. 용량 검증 로직(validateCapacityBeforeReserve) 추가. 예약 생성 및 이미지 업로드 오류 처리 개선.
마이페이지 컨트롤러 및 서비스
src/main/java/com/sku/refit/domain/mypage/controller/MyPageController.java, MyPageControllerImpl.java, src/main/java/com/sku/refit/domain/mypage/service/MyPageService.java, MyPageServiceImpl.java
getJoinedEvents()에 page/size 매개변수 추가. getMyPosts() 시그니처를 커서 기반(lastPostId)에서 페이지 기반(page, size)으로 변경, 반환 타입을 InfiniteResponse에서 MyPostsResponse로 변경.
마이페이지 DTO 및 매퍼
src/main/java/com/sku/refit/domain/mypage/dto/response/MyPageResponse.java, src/main/java/com/sku/refit/domain/mypage/mapper/MyPageMapper.java
JoinedEventsResponse에 페이지네이션 메타데이터(page, size, totalElements, totalPages, hasNext) 추가. 새로운 MyPostsResponse 중첩 DTO 추가. 매퍼 메서드 시그니처 업데이트(Page, Page 매개변수 추가).
티켓 저장소
src/main/java/com/sku/refit/domain/ticket/repository/TicketRepository.java
페이지네이션을 지원하는 findJoinedEventIds(userId, type, Pageable) 메서드 추가(@Query 사용).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 분

  • 특히 주의할 사항:
    • EventRepository의 새로운 @Query 메서드 로직 검증 (상태 결정 조건: startDate/endDate 비교)
    • EventMapperresolveStatus() 메서드 날짜 기반 상태 판정 로직 정확성
    • EventServiceImpl의 용량 검증(validateCapacityBeforeReserve) 및 예약 오류 처리 흐름
    • MyPageServiceImpl의 페이지네이션 마이그레이션 로직 (기존 커서 기반에서 페이지 기반으로 변경)
    • 에러 코드 재편성이 기존 클라이언트와 호환성 영향 없는지 확인

Possibly related PRs

  • Feature/event #7: 동일한 이벤트 도메인(컨트롤러, 서비스, 매퍼, 저장소, DTO/엔티티)의 클래스와 메서드(EventController/Impl, EventService, EventMapper, EventRepository, Event 엔티티/DTO)를 수정하고 있어 이 PR과 밀접한 관련이 있습니다.

Poem

🐰 날짜는 둘로 나뉘고, 용량은 더해지고,
상태는 셋으로 나뉘어 행사를 빛내네!
페이지 넘기며 사용자 마음 사로잡고,
커서는 물러나고 페이지가 춤을 춘다네. 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive PR 제목이 너무 모호하고 구체적이지 않습니다. 'Feature/manage'는 어떤 기능을 추가하는지 명확하게 전달하지 못합니다. 제목을 '관리자 행사 관리 기능 추가 (CRUD API 및 상태별 정렬)' 또는 '행사 관리 API 추가: 관리자 CRUD 및 페이지네이션' 으로 수정하세요.
✅ 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/manage

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.

@naooung naooung merged commit 02dd277 into main Dec 14, 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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (1)

333-370: 동시성 문제: 예약 용량 초과 가능성

validateCapacityBeforeReserve()increaseReservedCount() 사이에 TOCTOU (time-of-check to time-of-use) 레이스 컨디션이 존재합니다. 두 사용자가 동시에 마지막 자리를 예약하면 둘 다 검증을 통과하고 정원을 초과할 수 있습니다.

해결 방안:

  1. 비관적 잠금 (Pessimistic Locking): Repository에 @Lock(LockModeType.PESSIMISTIC_WRITE) 사용
  2. 낙관적 잠금 (Optimistic Locking): Event 엔티티에 @Version 필드 추가
  3. Atomic 업데이트: 단일 UPDATE 쿼리로 조건부 증가
// Repository 예시 (Pessimistic Lock)
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT e FROM Event e WHERE e.id = :id")
Optional<Event> findByIdWithLock(@Param("id") Long id);
🧹 Nitpick comments (12)
src/main/java/com/sku/refit/domain/mypage/dto/response/MyPageResponse.java (1)

156-165: 페이지네이션 응답 구조 중복 - 공통 추출 고려

MyPostsResponse, MyTicketsResponse, JoinedEventsResponse, EventPagedResponse 등 여러 DTO에서 동일한 페이지네이션 메타데이터 필드(page, size, totalElements, totalPages, hasNext)가 반복됩니다.

향후 유지보수성을 위해 제네릭 PagedResponse<T> 또는 PageMetadata 클래스로 추출하는 것을 고려해볼 수 있습니다.

// 예시: 공통 페이지네이션 응답 래퍼
@Getter
@Builder
public class PagedResponse<T> {
    private int page;
    private int size;
    private long totalElements;
    private int totalPages;
    private boolean hasNext;
    private List<T> items;
}
src/main/java/com/sku/refit/domain/event/entity/Event.java (2)

50-52: totalReservedCount의 null 체크 일관성 검토 필요

@Builder.Default로 0으로 초기화되지만, increaseReservedCount() (line 78)와 isFull() (line 83)에서 여전히 null 체크를 수행합니다. 기존 DB 데이터와의 호환성을 위한 것이라면 괜찮지만, 신규 코드라면 불필요한 방어 코드일 수 있습니다.


81-85: LGTM! isFull() 메서드가 올바르게 구현되었습니다.

capacity가 null일 때 무제한으로 처리하고, 예약 수와 정원을 비교하는 로직이 적절합니다. 이 메서드를 EventServiceImpl.validateCapacityBeforeReserve()에서도 활용하면 코드 중복을 줄일 수 있습니다.

src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (2)

379-395: 검증 메서드의 에러 코드 개선 제안

validateEventInfoRequestcreateEventupdateEvent 양쪽에서 호출되지만, null request에 대해 EVENT_CREATE_FAILED를 던집니다. updateEvent에서 호출 시 오해의 소지가 있을 수 있습니다.

  private void validateEventInfoRequest(EventInfoRequest request) {
    if (request == null) {
-     throw new CustomException(EventErrorCode.EVENT_CREATE_FAILED);
+     throw new CustomException(EventErrorCode.EVENT_INVALID_REQUEST);
    }

155-207: S3와 DB 작업 간의 트랜잭션 일관성 참고사항

S3 파일 삭제가 DB 삭제 전에 수행되므로, DB 작업 실패 시 S3 파일은 이미 삭제된 상태가 됩니다. 삭제 작업에서는 데이터 정합성보다 리소스 정리가 우선이므로 현재 구현이 적절합니다. 다만, 프로덕션 환경에서는 soft delete나 비동기 정리 작업을 고려해볼 수 있습니다.

src/main/java/com/sku/refit/domain/mypage/service/MyPageServiceImpl.java (2)

60-60: 사용되지 않는 필드 infiniteMapper

커서 기반 페이지네이션에서 페이지 기반으로 마이그레이션되면서 infiniteMapper 필드가 더 이상 사용되지 않는 것으로 보입니다. 사용되지 않는 필드와 import를 제거하는 것을 고려해 주세요.


93-114: getJoinedEvents에 에러 처리 누락

getMyTicketsgetMyPosts는 try-catch로 CustomException을 처리하고 MyPageErrorCode를 반환하지만, getJoinedEvents는 에러 처리가 없습니다. 일관성을 위해 동일한 패턴을 적용하는 것을 권장합니다.

   @Override
   public JoinedEventsResponse getJoinedEvents(int page, int size) {
     Long userId = userService.getCurrentUser().getId();
 
+    try {
       Pageable pageable = PageRequest.of(page, size);
       Page<Long> eventIdPage =
           ticketRepository.findJoinedEventIds(userId, TicketType.EVENT, pageable);
 
       List<Long> eventIdsOrdered = eventIdPage.getContent();
       if (eventIdsOrdered.isEmpty()) {
         return myPageMapper.toJoinedEventsResponse(Page.empty(pageable), List.of());
       }
 
       Map<Long, Event> eventMap =
           eventRepository.findAllById(eventIdsOrdered).stream()
               .collect(Collectors.toMap(Event::getId, Function.identity()));
 
       List<Event> orderedEvents =
           eventIdsOrdered.stream().map(eventMap::get).filter(Objects::nonNull).toList();
 
       return myPageMapper.toJoinedEventsResponse(eventIdPage, orderedEvents);
+    } catch (CustomException e) {
+      throw e;
+    } catch (Exception e) {
+      log.error("[MYPAGE] getJoinedEvents failed userId={}, page={}, size={}", userId, page, size, e);
+      throw new CustomException(MyPageErrorCode.JOINED_EVENTS_FETCH_FAILED);
+    }
   }
src/main/java/com/sku/refit/domain/mypage/controller/MyPageController.java (2)

52-53: 페이지네이션 파라미터에 기본값 추가 권장

pagesize 파라미터에 defaultValue를 설정하면 클라이언트가 파라미터를 생략할 수 있어 사용성이 향상됩니다. getMyTickets와 일관성을 유지하거나, 필수값으로 두려면 API 문서에 명시하는 것이 좋습니다.

   ResponseEntity<BaseResponse<JoinedEventsResponse>> getJoinedEvents(
-      @RequestParam int page, @RequestParam int size);
+      @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size);

59-60: getMyPosts에도 동일하게 기본값 추가 고려

일관성을 위해 getMyPosts에도 동일한 기본값 패턴을 적용하는 것을 고려해 주세요.

   ResponseEntity<BaseResponse<MyPostsResponse>> getMyPosts(
-      @RequestParam int page, @RequestParam int size);
+      @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size);
src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (1)

157-166: EventPagedResponse@Schema 어노테이션 누락

다른 DTO들(EventDetailResponse, EventListItem 등)과 달리 EventPagedResponse@Schema 어노테이션이 없습니다. Swagger 문서 일관성을 위해 추가하는 것을 권장합니다.

   @Getter
   @Builder
+  @Schema(title = "EventPagedResponse DTO", description = "행사 페이지 응답")
   public static class EventPagedResponse {
+    @Schema(description = "현재 페이지 번호", example = "0")
     private int page;
+    @Schema(description = "페이지 크기", example = "10")
     private int size;
+    @Schema(description = "전체 요소 수", example = "100")
     private long totalElements;
+    @Schema(description = "전체 페이지 수", example = "10")
     private int totalPages;
+    @Schema(description = "다음 페이지 존재 여부", example = "true")
     private boolean hasNext;
+    @Schema(description = "행사 목록")
     private List<EventListItem> items;
   }
src/main/java/com/sku/refit/domain/event/repository/EventRepository.java (2)

23-26: Pageable 사용 시 반환 타입 검토 필요

Pageable을 파라미터로 받지만 List<Event>를 반환하면 전체 페이지 수, 총 요소 수 등의 페이지네이션 메타데이터가 손실됩니다.

의도적으로 결과 제한만 필요한 경우라면 현재 구현도 동작하지만, 페이지네이션 정보가 필요하다면 Page<Event> 또는 Slice<Event>로 변경을 권장합니다.

-  List<Event> findByStartDateGreaterThanEqualOrderByStartDateAsc(
-      LocalDate today, Pageable pageable);
-
-  List<Event> findByEndDateLessThanOrderByEndDateDesc(LocalDate today, Pageable pageable);
+  Page<Event> findByStartDateGreaterThanEqualOrderByStartDateAsc(
+      LocalDate today, Pageable pageable);
+
+  Page<Event> findByEndDateLessThanOrderByEndDateDesc(LocalDate today, Pageable pageable);

34-65: 복잡한 정렬 로직 구현 확인

CASE WHEN을 활용한 상태별 우선순위 정렬과 상태 내 세부 정렬 로직이 요구사항대로 잘 구현되어 있습니다. tie-breaker로 e.id desc를 사용한 것도 적절합니다.

다만, lower(e.name) like lower(concat('%', :q, '%')) 패턴은 양쪽 와일드카드로 인해 인덱스를 활용하지 못합니다. 현재 데이터 규모에서는 문제없지만, 이벤트 수가 크게 증가하면 Full-Text Search 인덱스 도입을 고려해 보세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b11d9ab and c49de4e.

📒 Files selected for processing (18)
  • src/main/java/com/sku/refit/domain/event/controller/EventController.java (3 hunks)
  • src/main/java/com/sku/refit/domain/event/controller/EventControllerImpl.java (2 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 (5 hunks)
  • src/main/java/com/sku/refit/domain/event/entity/Event.java (3 hunks)
  • src/main/java/com/sku/refit/domain/event/entity/EventStatus.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 (7 hunks)
  • src/main/java/com/sku/refit/domain/event/repository/EventRepository.java (1 hunks)
  • src/main/java/com/sku/refit/domain/event/service/EventService.java (2 hunks)
  • src/main/java/com/sku/refit/domain/event/service/EventServiceImpl.java (10 hunks)
  • src/main/java/com/sku/refit/domain/mypage/controller/MyPageController.java (1 hunks)
  • src/main/java/com/sku/refit/domain/mypage/controller/MyPageControllerImpl.java (1 hunks)
  • src/main/java/com/sku/refit/domain/mypage/dto/response/MyPageResponse.java (3 hunks)
  • src/main/java/com/sku/refit/domain/mypage/mapper/MyPageMapper.java (3 hunks)
  • src/main/java/com/sku/refit/domain/mypage/service/MyPageService.java (1 hunks)
  • src/main/java/com/sku/refit/domain/mypage/service/MyPageServiceImpl.java (3 hunks)
  • src/main/java/com/sku/refit/domain/ticket/repository/TicketRepository.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
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-45)
  • Getter (47-73)
src/main/java/com/sku/refit/domain/event/entity/EventStatus.java (1)
src/main/java/com/sku/refit/domain/event/dto/response/EventResponse.java (9)
  • Schema (14-193)
  • Getter (17-66)
  • Getter (68-78)
  • Getter (80-93)
  • Getter (97-119)
  • Getter (121-140)
  • Getter (142-155)
  • Getter (157-166)
  • Getter (168-192)
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 (1)
  • Schema (14-193)
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-74)
src/main/java/com/sku/refit/domain/event/mapper/EventMapper.java (1)
src/main/java/com/sku/refit/domain/event/dto/request/EventRequest.java (1)
  • EventRequest (13-74)
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-74)
⏰ 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 (17)
src/main/java/com/sku/refit/domain/event/service/EventService.java (1)

162-171: LGTM!

새로운 getEvents 메서드의 인터페이스 설계와 Javadoc 문서화가 잘 되어 있습니다. 페이지네이션, 상태 필터, 검색어 파라미터가 명확하게 정의되어 있습니다.

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

1-24: LGTM!

EventStatus enum이 깔끔하게 구현되어 있습니다. Lombok 어노테이션과 Swagger 스키마 문서화가 적절하게 적용되었습니다.

src/main/java/com/sku/refit/domain/mypage/dto/response/MyPageResponse.java (1)

77-86: 페이지네이션 메타데이터 필드 추가 확인됨

JoinedEventsResponse에 페이지네이션 메타데이터가 적절히 추가되었습니다.

src/main/java/com/sku/refit/domain/mypage/controller/MyPageControllerImpl.java (1)

29-37: LGTM! 페이지 기반 페이지네이션으로의 마이그레이션이 올바르게 구현되었습니다.

컨트롤러 구현이 인터페이스 및 서비스 레이어와 잘 정렬되어 있습니다. pagesize 파라미터에 대한 음수 값 검증은 서비스 레이어 또는 Spring의 @Min 어노테이션으로 처리하는 것을 고려해 볼 수 있습니다.

src/main/java/com/sku/refit/domain/mypage/service/MyPageService.java (1)

10-55: LGTM! 서비스 인터페이스가 잘 문서화되어 있습니다.

페이지네이션 파라미터, 정렬 기준, 반환 타입에 대한 Javadoc이 명확하게 작성되어 있습니다. 페이지 번호가 0부터 시작한다는 점도 Spring Data 표준과 일치합니다.

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

157-168: LGTM! 이벤트 상태 결정 로직이 PR 요구사항과 일치합니다.

  • UPCOMING: startDate > today
  • ENDED: endDate < today
  • ONGOING: startDate <= today <= endDate

경계값 처리도 올바르게 구현되어 있습니다.


129-155: LGTM! 페이지네이션 응답 매핑이 잘 구현되었습니다.

Page 객체에서 메타데이터를 추출하고, 각 아이템에 대해 상태를 계산하는 로직이 올바릅니다. totalReservedCount에 대한 null 체크도 방어적 코딩으로 적절합니다.

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

411-457: LGTM! 이벤트 목록 조회 로직이 잘 구현되었습니다.

상태별 필터링, 검색어 처리, 에러 핸들링이 적절합니다. 로깅에 필요한 파라미터가 모두 포함되어 있어 디버깅에 유용합니다.

선택적 개선: Java 14+를 사용 중이라면 if-else 체인을 switch expression으로 리팩터링하여 가독성을 높일 수 있습니다.

src/main/java/com/sku/refit/domain/mypage/mapper/MyPageMapper.java (2)

93-115: LGTM!

toJoinedEventItem에서 startDate를 사용하도록 변경하고, 새로운 toJoinedEventsResponse 메서드가 페이지네이션 메타데이터를 일관되게 처리합니다.


150-159: LGTM!

toMyPostsResponse 메서드가 다른 페이지네이션 응답 매퍼들과 일관된 패턴으로 구현되었습니다.

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

168-192: LGTM!

EventListItem DTO가 관리자 목록 조회에 필요한 필드들(eventId, name, startDate, location, reservedCount, capacity, status)과 함께 적절한 @Schema 어노테이션으로 문서화되어 있습니다.

src/main/java/com/sku/refit/domain/event/exception/EventErrorCode.java (1)

15-42: LGTM!

에러 코드가 논리적으로 그룹화되어 있습니다:

  • 01x: 유효성 검증
  • 02x: 파일 업로드/삭제
  • 03x: CRUD 작업
  • 04x: 예약 관련
  • 05x: 이미지 관련
  • 06x: 목록 조회

HTTP 상태 코드도 적절하게 매핑되어 있습니다.

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

189-211: reserveEvent API 문서 개선됨

멀티파트 요청 구조가 명확하게 문서화되어 있습니다. clothImageList가 선택 사항임을 required = false로 명시한 점이 좋습니다.


92-126: LGTM! 관리자 행사 목록 조회 API 추가

페이지네이션, 상태 필터링, 검색 기능이 잘 설계되었습니다. API 문서에 상태 판정 기준과 정렬 로직이 명확하게 기술되어 있습니다.

관리자 전용 엔드포인트(/admin/**)는 SecurityConfig에서 .requestMatchers(RegexRequestMatcher.regexMatcher(".*/admin($|/.*)")).hasRole("ADMIN")으로 적절히 보호되고 있습니다.

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

70-81: UPCOMING 필터 쿼리 적절

startDate > :today 조건과 startDate ASC 정렬이 요구사항과 일치합니다.


84-95: ONGOING 필터 쿼리 적절

startDate <= :today AND endDate >= :today 조건과 startDate DESC 정렬이 요구사항과 일치합니다.


98-109: ENDED 필터 쿼리 적절

endDate < :today 조건과 endDate DESC 정렬이 요구사항과 일치합니다.

Comment on lines +30 to +37
@NotNull(message = "행사 시작 날짜는 필수입니다.") @Schema(description = "시작 날짜", example = "2025-12-24")
private LocalDate startDate;

@NotNull(message = "행사 종료 날짜는 필수입니다.") @Schema(description = "종료 날짜", example = "2025-12-26")
private LocalDate endDate;

@Schema(description = "예약 정원", example = "100")
private Integer capacity;
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

날짜 범위 및 capacity 유효성 검증 누락

  1. startDateendDate 간의 관계 검증이 없습니다. endDatestartDate보다 이전일 경우 유효하지 않은 데이터가 저장될 수 있습니다.
  2. capacity 필드에 최소값 제약이 없어 0 또는 음수가 입력될 수 있습니다.

날짜 범위 검증은 커스텀 validator나 서비스 레이어에서 처리할 수 있으며, capacity에는 기본 제약을 추가하는 것을 권장합니다:

  @Schema(description = "예약 정원", example = "100")
+ @Positive(message = "예약 정원은 1 이상이어야 합니다.")
  private Integer capacity;

날짜 범위 검증이 서비스 레이어에서 처리되고 있는지 확인이 필요합니다:

#!/bin/bash
# 서비스에서 날짜 검증 로직 확인
rg -n "startDate.*endDate|endDate.*startDate|isBefore|isAfter" --type java -C 3

Comment on lines +36 to +47
@Query(
"""
select t.targetId
from Ticket t
where t.userId = :userId
and t.type = :type
and t.usedAt is not null
group by t.targetId
order by max(t.usedAt) desc
""")
Page<Long> findJoinedEventIds(
@Param("userId") Long userId, @Param("type") TicketType type, Pageable pageable);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check the actual TicketRepository file and look for countQuery definition
rg -A 20 "findJoinedEventIds" --type java -B 5

Repository: refit-lab/refit-be

Length of output: 3778


🏁 Script executed:

# Search for any test files related to TicketRepository
fd "TicketRepository" --type f -e java

Repository: refit-lab/refit-be

Length of output: 136


🏁 Script executed:

# Look for Spring Data JPA related dependencies or version
fd "pom.xml\|build.gradle\|gradle.properties" --type f

Repository: refit-lab/refit-be

Length of output: 44


🏁 Script executed:

# Find build configuration files
find . -name "pom.xml" -o -name "build.gradle" -o -name "gradle.properties" | head -5

Repository: refit-lab/refit-be

Length of output: 76


🏁 Script executed:

# Search for test files related to TicketRepository
find . -path "*test*" -name "*Ticket*" -type f

Repository: refit-lab/refit-be

Length of output: 44


🏁 Script executed:

# Check the full TicketRepository file to see if there's any countQuery
cat -n src/main/java/com/sku/refit/domain/ticket/repository/TicketRepository.java

Repository: refit-lab/refit-be

Length of output: 1908


🏁 Script executed:

# Check Spring Data JPA version in build.gradle
cat build.gradle | grep -A 50 -B 5 "spring-boot\|data-jpa\|jpa"

Repository: refit-lab/refit-be

Length of output: 2492


🏁 Script executed:

# Search for any test files that might test pagination with this method
find . -type f -name "*.java" -path "*test*" | xargs grep -l "findJoinedEventIds" 2>/dev/null

Repository: refit-lab/refit-be

Length of output: 46


🏁 Script executed:

# Check if there are any integration or unit tests for TicketRepository at all
find . -path "*test*" -type f -name "*.java" | xargs grep -l "TicketRepository" 2>/dev/null

Repository: refit-lab/refit-be

Length of output: 46


🏁 Script executed:

# Check Spring Boot version to understand which Spring Data JPA version is being used
cat build.gradle | grep -i "spring.boot"

Repository: refit-lab/refit-be

Length of output: 590


🏁 Script executed:

# Check if there's a src/test directory at all
find . -type d -name "test" | head -5

Repository: refit-lab/refit-be

Length of output: 72


🏁 Script executed:

# Verify the actual Spring Boot version by checking gradle wrapper or properties
cat gradle.properties 2>/dev/null || find . -name "gradle-wrapper.properties" -exec cat {} \;

Repository: refit-lab/refit-be

Length of output: 314


명시적 countQuery 추가 필요

GROUP BY를 사용하는 JPQL 쿼리에서 Page<Long>을 반환할 때, Spring Data JPA가 자동 생성하는 count 쿼리가 올바르게 동작하지 않을 수 있습니다. 현재 코드는 countQuery를 명시적으로 정의하지 않았으므로, 페이지네이션의 총 개수 계산이 정확하지 않을 수 있습니다.

@Query 어노테이션에 countQuery 파라미터를 추가하세요:

  @Query(
+     value =
      """
      select t.targetId
      from Ticket t
      where t.userId = :userId
        and t.type = :type
        and t.usedAt is not null
      group by t.targetId
      order by max(t.usedAt) desc
-      """)
+      """,
+     countQuery =
+         """
+     select count(distinct t.targetId)
+     from Ticket t
+     where t.userId = :userId
+       and t.type = :type
+       and t.usedAt is not null
+     """)
📝 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
@Query(
"""
select t.targetId
from Ticket t
where t.userId = :userId
and t.type = :type
and t.usedAt is not null
group by t.targetId
order by max(t.usedAt) desc
""")
Page<Long> findJoinedEventIds(
@Param("userId") Long userId, @Param("type") TicketType type, Pageable pageable);
@Query(
value =
"""
select t.targetId
from Ticket t
where t.userId = :userId
and t.type = :type
and t.usedAt is not null
group by t.targetId
order by max(t.usedAt) desc
""",
countQuery =
"""
select count(distinct t.targetId)
from Ticket t
where t.userId = :userId
and t.type = :type
and t.usedAt is not null
""")
Page<Long> findJoinedEventIds(
@Param("userId") Long userId, @Param("type") TicketType type, Pageable pageable);
🤖 Prompt for AI Agents
In src/main/java/com/sku/refit/domain/ticket/repository/TicketRepository.java
around lines 36 to 47, the JPQL query uses GROUP BY and returns Page<Long> but
lacks an explicit countQuery, which can make Spring Data JPA compute the total
incorrectly; add a countQuery parameter to the @Query annotation that mirrors
the WHERE conditions and returns count(distinct t.targetId) (e.g. "select
count(distinct t.targetId) from Ticket t where t.userId = :userId and t.type =
:type and t.usedAt is not null") so pagination total is calculated correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feature 새로운 기능 요청 🟠 priority: medium 중간 우선순위 ♻️ refactor 리팩토링 작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨Feat: 관리자 페이지 관련 기능 개발

2 participants