-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/manage #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/manage #17
Conversation
Walkthrough이벤트 도메인의 날짜 필드를 단일 date에서 startDate/endDate로 분리하고, capacity 필드를 추가했습니다. 또한 EventStatus 열거형을 도입하고, 이벤트 목록 조회에 페이지네이션과 상태 필터링을 지원하는 새로운 엔드포인트와 쿼리 메서드를 추가했습니다. MyPage 영역도 커서 기반 페이지네이션에서 페이지 기반 페이지네이션으로 마이그레이션했습니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 분
Possibly related PRs
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.
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) 레이스 컨디션이 존재합니다. 두 사용자가 동시에 마지막 자리를 예약하면 둘 다 검증을 통과하고 정원을 초과할 수 있습니다.해결 방안:
- 비관적 잠금 (Pessimistic Locking): Repository에
@Lock(LockModeType.PESSIMISTIC_WRITE)사용- 낙관적 잠금 (Optimistic Locking): Event 엔티티에
@Version필드 추가- 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: 검증 메서드의 에러 코드 개선 제안
validateEventInfoRequest가createEvent와updateEvent양쪽에서 호출되지만, 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에 에러 처리 누락
getMyTickets와getMyPosts는 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: 페이지네이션 파라미터에 기본값 추가 권장
page와size파라미터에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
📒 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!
EventStatusenum이 깔끔하게 구현되어 있습니다. 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! 페이지 기반 페이지네이션으로의 마이그레이션이 올바르게 구현되었습니다.컨트롤러 구현이 인터페이스 및 서비스 레이어와 잘 정렬되어 있습니다.
page와size파라미터에 대한 음수 값 검증은 서비스 레이어 또는 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!
EventListItemDTO가 관리자 목록 조회에 필요한 필드들(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:reserveEventAPI 문서 개선됨멀티파트 요청 구조가 명확하게 문서화되어 있습니다.
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정렬이 요구사항과 일치합니다.
src/main/java/com/sku/refit/domain/event/controller/EventControllerImpl.java
Show resolved
Hide resolved
| @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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
날짜 범위 및 capacity 유효성 검증 누락
startDate와endDate간의 관계 검증이 없습니다.endDate가startDate보다 이전일 경우 유효하지 않은 데이터가 저장될 수 있습니다.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| @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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Check the actual TicketRepository file and look for countQuery definition
rg -A 20 "findJoinedEventIds" --type java -B 5Repository: refit-lab/refit-be
Length of output: 3778
🏁 Script executed:
# Search for any test files related to TicketRepository
fd "TicketRepository" --type f -e javaRepository: 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 fRepository: 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 -5Repository: refit-lab/refit-be
Length of output: 76
🏁 Script executed:
# Search for test files related to TicketRepository
find . -path "*test*" -name "*Ticket*" -type fRepository: 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.javaRepository: 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/nullRepository: 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/nullRepository: 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 -5Repository: 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.
| @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.
✨ 새로운 기능
🛠 개발 상세
POST /api/events/admin: 행사 생성 (multipart, 썸네일 포함)PUT /api/events/admin/{id}: 행사 수정DELETE /api/events/admin/{id}:행사 및 관련 이미지 전체 삭제GET /api/events/adminUPCOMING / ONGOING / ENDEDUPCOMING:startDate > todayONGOING:startDate <= today <= endDateENDED:endDate < todayONGOING → UPCOMING → ENDEDstartDate DESCstartDate ASCendDate DESC🔗 관련 문서 / 이슈
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.