-
Notifications
You must be signed in to change notification settings - Fork 2
feat: admin 행사 리스트 조회 api 구현 - #113 #114
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
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdmin 이벤트 리스트 조회 API가 추가되었다. 컨트롤러, 서비스, DTO가 신설되며 이벤트/티켓 라운드/티켓 타입 조회 컴포넌트와 저장소 메서드가 추가·정리되었다. 날짜 유틸에 포맷터가 추가되었다. AdminExceptionHandler 패키지가 변경되었고, dev 로그백 설정이 교체되었다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Admin as Admin Client
participant C as AdminEventController
participant S as AdminEventService
participant ER as AdminEventRetriever
participant RR as AdminTicketRoundRetriever
participant TR as AdminTicketTypeRetriever
participant ERepo as EventRepository
participant RRepo as TicketRoundRepository
participant TRepo as TicketTypeRepository
Admin->>C: GET /api/admin/events
C->>S: getEvents()
S->>ER: getAllEvents()
ER->>ERepo: findAll()
ERepo-->>ER: List<EventEntity>
ER-->>S: List<Event>
S->>RR: getTicketRoundsByEventIds(eventIds)
RR->>RRepo: findByEventIdIn(eventIds)
RRepo-->>RR: List<TicketRoundEntity>
RR-->>S: List<TicketRound>
S->>TR: getTicketTypesByTicketRounds(roundIds)
TR->>TRepo: findByTicketRoundIdIn(roundIds)
TRepo-->>TR: List<TicketTypeEntity>
TR-->>S: List<TicketType>
note over S: 이벤트별 판매 수량 집계,<br/>날짜 포맷 및 월별 그룹화
S-->>C: List<EventListResponse>
C-->>Admin: 200 OK (BaseResponse)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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
🧹 Nitpick comments (22)
src/main/java/com/permitseoul/permitserver/domain/admin/base/AdminExceptionHandler.java (1)
16-19: 예외 로깅 추가 제안관리자 API 예외는 서버 로그에 남기는 편이 운영상 유리합니다. 반환 직전에 경고 로그를 추가하세요.
@ExceptionHandler(AdminApiException.class) public ResponseEntity<BaseResponse<?>> handleAdminApiException(final AdminApiException e) { - return ApiResponseUtil.failure(e.getErrorCode()); + log.warn("Admin API error: code={}, message={}", e.getErrorCode(), e.getMessage(), e); + return ApiResponseUtil.failure(e.getErrorCode()); }클래스 상단에 로거 필드를 추가해야 합니다:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; // ... private static final Logger log = LoggerFactory.getLogger(AdminExceptionHandler.class);src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/res/EventListResponse.java (1)
5-15: 날짜 문자열 노출 대신 ISO 원본 필드 병행 권고eventDate/eventYearMonth를 포맷 문자열로만 노출하면 i18n/타임존 이슈가 생깁니다. ISO-8601(LocalDate/YearMonth 또는 Instant) 원본 필드를 함께 노출(또는 교체)해 주세요. 클라이언트 포맷팅으로 위임하는 것이 안전합니다.
src/main/resources/logback/logback-dev-info.xml (1)
1-27: dev 파일 로깅: 절대 경로/스택트레이스/성능 개선/home/ubuntu 고정 경로는 개발 환경 다양성에 취약합니다. 환경변수 기반 경로로 교체하고, JSON 로그에 스택트레이스를 포함하며(문제 분석 용이), 필요 시 AsyncAppender로 디스크 I/O 영향 최소화하세요.
경로/스택트레이스 반영 diff:
<appender name="DEVINFOROLLFILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 파일 위치 --> - <file>/home/ubuntu/logs/PERMIT-INFO-LOG.log</file> + <file>${LOG_DIR:-./logs}/PERMIT-INFO-LOG.log</file> @@ <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> @@ <message/> + <stackTrace/> </providers> </encoder> @@ <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> - <fileNamePattern>/home/ubuntu/logs/PERMIT-INFO-LOG-%d{yyyy-MM-dd}-%i-log.zip</fileNamePattern> + <fileNamePattern>${LOG_DIR:-./logs}/PERMIT-INFO-LOG-%d{yyyy-MM-dd}-%i-log.zip</fileNamePattern> <maxFileSize>50MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>3GB</totalSizeCap> </rollingPolicy> </appender>AsyncAppender 추가 예:
<appender name="ASYNC_DEVINFO" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="DEVINFOROLLFILE"/> </appender>src/main/java/com/permitseoul/permitserver/domain/tickettype/core/repository/TicketTypeRepository.java (1)
11-23: TicketTypeRepository 리팩터 권장 및 메서드 제거 확인
findByTicketRoundIdIn(final List<Long>)→Collection<Long>로 일반화 권장findByTicketRoundId(long)호출 흔적 없음 확인- 대량 IN 조회 최적화를 위해
ticket_type.ticket_round_id컬럼에 인덱스 존재 여부 검증 필요src/main/java/com/permitseoul/permitserver/domain/ticketround/core/repository/TicketRoundRepository.java (1)
18-19: findByEventIdIn: 파라미터 일반화 및 정렬 일관성 고려Collection을 받아 호출 유연성을 확보하고, 후속 로직이 정렬을 가정한다면 OrderBy를 메서드 시그니처에 반영하는 것도 고려하세요.
- List<TicketRoundEntity> findByEventIdIn(final List<Long> eventIds); + List<TicketRoundEntity> findByEventIdIn(final Collection<Long> eventIds); + // 필요 시: + // List<TicketRoundEntity> findByEventIdInOrderBySalesStartDateAsc(final Collection<Long> eventIds);대량 IN 조회 빈도/규모가 크다면 event_id 인덱스 존재 여부를 확인하고, 필요 시 배치 크기를 조절하거나 스트리밍 조회를 검토해 주세요.
src/main/java/com/permitseoul/permitserver/domain/admin/ticketround/core/AdminTicketRoundRetriever.java (4)
1-1: 패키지 경로 불일치(component 누락) — 패키지/폴더를 일관되게 정리 권장동일 성격의 리트리버들이
.core.component패키지에 위치하는데, 본 파일만.core에 있어 혼선 소지가 있습니다. 파일 이동까지 포함해 통일을 권장합니다.-package com.permitseoul.permitserver.domain.admin.ticketround.core; +package com.permitseoul.permitserver.domain.admin.ticketround.core.component;
3-7: 읽기 전용 트랜잭션 적용지연 로딩(LAZY) 접근 시점을 안전하게 보장하고, 불필요한 flush 방지를 위해 읽기 메서드에 readOnly 트랜잭션을 권장합니다.
import com.permitseoul.permitserver.domain.ticketround.core.repository.TicketRoundRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional;
15-19: null/빈 컬렉션 처리 및 ID 중복 제거로 방어적 코딩빈 리스트에 대한 불필요 쿼리 방지, ID 중복으로 인한 DB work 낭비를 줄이세요. 또한 JDK 호환성(JDK 11/8) 이슈가 있으면 Collectors로 대체하세요.
- public List<TicketRound> getTicketRoundsByEventIds(final List<Long> eventIds) { - return ticketRoundRepository.findByEventIdIn(eventIds).stream() - .map(TicketRound::fromEntity) - .toList(); - } + @Transactional(readOnly = true) + public List<TicketRound> getTicketRoundsByEventIds(final List<Long> eventIds) { + if (eventIds == null || eventIds.isEmpty()) { + return List.of(); + } + final List<Long> uniqueIds = eventIds.stream().distinct().toList(); // JDK 16+ 사용 중이 아니면 Collectors.toList() 사용 + return ticketRoundRepository.findByEventIdIn(uniqueIds).stream() + .map(TicketRound::fromEntity) + .toList(); + }
15-19: IN 조회 컬럼 인덱스 확인 권장(event_id)
findByEventIdIn경로가 커질수록 스캔 비용이 큽니다.ticket_round(event_id)인덱스 유무를 확인해 주세요.필요 시 DDL/마이그레이션 스크립트 작성 도와드릴게요.
src/main/java/com/permitseoul/permitserver/domain/admin/tickettype/core/component/AdminTicketTypeRetriever.java (3)
10-12: 읽기 전용 트랜잭션 적용조회성 컴포넌트에 readOnly 트랜잭션을 적용해 세션/지연로딩 이슈를 줄이세요.
@Component @RequiredArgsConstructor public class AdminTicketTypeRetriever {추가 임포트:
import org.springframework.transaction.annotation.Transactional;
15-19: 빈/중복 ID 처리 및 스트림 수집 호환성방어적 코딩과 불필요한 DB work 최소화.
- public List<TicketType> getTicketTypesByTicketRounds(final List<Long> ticketRoundIds) { - return ticketTypeRepository.findByTicketRoundIdIn(ticketRoundIds).stream() - .map(TicketType::fromEntity) - .toList(); - } + @Transactional(readOnly = true) + public List<TicketType> getTicketTypesByTicketRounds(final List<Long> ticketRoundIds) { + if (ticketRoundIds == null || ticketRoundIds.isEmpty()) { + return List.of(); + } + final List<Long> uniqueIds = ticketRoundIds.stream().distinct().toList(); + return ticketTypeRepository.findByTicketRoundIdIn(uniqueIds).stream() + .map(TicketType::fromEntity) + .toList(); + }
15-19: IN 조회 컬럼 인덱스 확인 권장(ticket_round_id)
ticket_type(ticket_round_id)인덱스를 점검해 주세요.src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (4)
19-23: 포맷터 추가 LGTM영문 로케일 고정 의도가 명확하면 현재 구현 적절합니다. 다국어 요구가 있다면 Locale 주입/파라미터화를 고려하세요.
51-54: formatDayWithDate: null 방어 또는 계약 문서화입력이 null이면 NPE가 납니다. 사전 검증 또는 Javadoc/메서드명에 계약 명시를 권장합니다.
- public static String formatDayWithDate(final LocalDateTime dateTime) { - return dateTime.format(DAY_DD_FORMATTER); - } + public static String formatDayWithDate(final LocalDateTime dateTime) { + if (dateTime == null) throw new DateFormatException(ErrorCode.INTERNAL_ISO_DATE_ERROR); + return dateTime.format(DAY_DD_FORMATTER); + }
56-59: formatYearMonth: 입력 검증 동일 적용 제안위와 동일한 형태로 null 방어 일관성 유지 추천.
28-49: 기간 포맷: 월/연도 교차 구간 표현 보강(예: May–Jun 2025, Dec 2025–Jan 2026)시작/종료가 월·연도를 넘을 때
endDate기준만 표기되어 정보가 축약됩니다. en dash 사용 일관성도 맞추면 좋습니다.- private static final String DASH = "-"; + private static final String EN_DASH = "–"; @@ - } else { // 시작날짜와 종료날짜가 다를 때, "Sun–Mon, 25–26 May 2025" 형식으로 포맷팅 - sb.append(startDate.format(DAY_FORMATTER)) - .append(DASH) - .append(endDate.format(DAY_FORMATTER)) - .append(COMMA_AND_SPACE) - .append(startDate.format(DATE_FORMATTER)) - .append(DASH) - .append(endDate.format(DATE_FORMATTER)) - .append(SPACE) - .append(endDate.format(MONTH_YEAR_FORMATTER)); + } else { // 예: "Sun–Mon, 31 May–01 Jun 2025" 또는 "Thu–Fri, 31 Dec 2025–01 Jan 2026" + sb.append(startDate.format(DAY_FORMATTER)) + .append(EN_DASH) + .append(endDate.format(DAY_FORMATTER)) + .append(COMMA_AND_SPACE) + .append(startDate.format(DATE_FORMATTER)) + .append(SPACE) + .append(startDate.format(DateTimeFormatter.ofPattern("MMM", Locale.ENGLISH))) + .append(EN_DASH) + .append(endDate.format(DATE_FORMATTER)) + .append(SPACE) + .append(endDate.format(DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH))); + // 연도 교차 시 끝 연도만 표기되는 문제 해소 }src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (2)
16-22: 대량 데이터 대비: 페이징/정렬 지원 필요
findAll()은 데이터 증가 시 메모리/성능 병목이 됩니다. 기본 정렬(예: 시작일 최신순)과 페이징을 리트리버 단계에서 지원하세요.- public List<Event> getAllEvents() { - final List<EventEntity> eventEntities = eventRepository.findAll(); + public List<Event> getAllEvents(final org.springframework.data.domain.Sort sort) { + final List<EventEntity> eventEntities = eventRepository.findAll(sort); return eventEntities.stream() .map(Event::fromEntity) .toList(); }대안:
Page<Event>반환으로 메타데이터 노출.리스트를 소비하는
AdminEventService.getEvents()영향도 확인 바랍니다.
11-14: 읽기 전용 트랜잭션 적용조회 계층에 readOnly 트랜잭션을 적용해 영속성 컨텍스트 관리와 성능 최적화에 도움됩니다.
@Component @RequiredArgsConstructor public class AdminEventRetriever { private final EventRepository eventRepository;추가 임포트/어노테이션 예:
import org.springframework.transaction.annotation.Transactional; @Transactional(readOnly = true) public class AdminEventRetriever { ... }src/main/java/com/permitseoul/permitserver/domain/admin/event/api/controller/AdminEventController.java (1)
19-23: 페이징/정렬 파라미터 수용 제안관리 화면 리스트 특성상 페이징 필요성이 큽니다. 컨트롤러에서
Pageable을 받아 서비스로 위임하는 방식을 권장합니다.- public ResponseEntity<BaseResponse<?>> getEvents() { - return ApiResponseUtil.success(SuccessCode.OK, adminEventService.getEvents()); - } + public ResponseEntity<BaseResponse<?>> getEvents( + @org.springframework.data.web.PageableDefault(size = 20) org.springframework.data.domain.Pageable pageable + ) { + return ApiResponseUtil.success(SuccessCode.OK, adminEventService.getEvents(pageable)); + }src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (3)
55-76: 빈 이벤트 ID 집합일 때 불필요한 저장소 호출 방지
eventIds가 비어있으면 불필요한getTicketRoundsByEventIds호출이 발생합니다. 빠르게 반환하도록 해주세요.private void aggregateSoldCounts(final List<Event> events, final Map<Long, Integer> soldByEventId) { final List<Long> eventIds = extractEventIds(events); + if (eventIds.isEmpty()) { + return; + } final List<TicketRound> rounds = adminTicketRoundRetriever.getTicketRoundsByEventIds(eventIds);
47-53: 초기 맵 용량과 ID null 방어로 미세 최적화초기 용량을 지정해 리사이즈를 줄이고, ID null을 배제하세요.
- final Map<Long, Integer> map = new HashMap<>(); + final Map<Long, Integer> map = new HashMap<>(Math.max(16, events.size())); for (Event e : events) { - if (e != null) map.put(e.getEventId(), 0); + if (e != null && e.getEventId() != null) map.put(e.getEventId(), 0); }
40-45: 반환 리스트 불변화(옵션)외부에서 결과 리스트를 변경하지 못하도록 불변 리스트로 반환하는 것을 권장합니다.
- return result; + return Collections.unmodifiableList(result);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (11)
src/main/java/com/permitseoul/permitserver/domain/admin/base/AdminExceptionHandler.java(1 hunks)src/main/java/com/permitseoul/permitserver/domain/admin/event/api/controller/AdminEventController.java(1 hunks)src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/res/EventListResponse.java(1 hunks)src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java(1 hunks)src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java(1 hunks)src/main/java/com/permitseoul/permitserver/domain/admin/ticketround/core/AdminTicketRoundRetriever.java(1 hunks)src/main/java/com/permitseoul/permitserver/domain/admin/tickettype/core/component/AdminTicketTypeRetriever.java(1 hunks)src/main/java/com/permitseoul/permitserver/domain/ticketround/core/repository/TicketRoundRepository.java(2 hunks)src/main/java/com/permitseoul/permitserver/domain/tickettype/core/repository/TicketTypeRepository.java(1 hunks)src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java(2 hunks)src/main/resources/logback/logback-dev-info.xml(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/com/permitseoul/permitserver/domain/admin/ticketround/core/AdminTicketRoundRetriever.java (2)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (1)
Component(11-24)src/main/java/com/permitseoul/permitserver/domain/admin/tickettype/core/component/AdminTicketTypeRetriever.java (1)
Component(10-20)
src/main/java/com/permitseoul/permitserver/domain/admin/tickettype/core/component/AdminTicketTypeRetriever.java (2)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (1)
Component(11-24)src/main/java/com/permitseoul/permitserver/domain/admin/ticketround/core/AdminTicketRoundRetriever.java (1)
Component(10-20)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (2)
src/main/java/com/permitseoul/permitserver/domain/admin/ticketround/core/AdminTicketRoundRetriever.java (1)
Component(10-20)src/main/java/com/permitseoul/permitserver/domain/admin/tickettype/core/component/AdminTicketTypeRetriever.java (1)
Component(10-20)
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/controller/AdminEventController.java (1)
src/main/java/com/permitseoul/permitserver/global/response/ApiResponseUtil.java (1)
ApiResponseUtil(7-29)
🔇 Additional comments (4)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (1)
16-22: JDK 호환성: Stream.toList() 사용 가능
프로젝트의 Gradle toolchain이JavaLanguageVersion.of(17)으로 설정되어 있어 Stream.toList() 호환성 이슈가 없습니다. 변경 불필요합니다.src/main/java/com/permitseoul/permitserver/domain/admin/event/api/controller/AdminEventController.java (1)
19-23: 관리자 권한은 글로벌 SecurityConfig에서 이미 적용됨
SecurityConfig의 authorizeHttpRequests에서.requestMatchers(adminURIList).hasRole(UserRole.ADMIN.name())로 ADMIN 접근 제어가 설정되어 있어, 해당 엔드포인트에 별도@PreAuthorize추가는 선택 사항입니다.src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (2)
27-45: 전체 흐름과 배치 조회/그룹화 구성은 좋습니다
- 읽기 전용 트랜잭션 범위, 일괄 조회 후 월별 그룹화, DTO 매핑까지 플로우가 깔끔합니다.
123-141: 추가 확인 결과를 요청했습니다.
| private List<Long> extractEventIds(final List<Event> events) { | ||
| final List<Long> eventIds = new ArrayList<>(); | ||
| for (Event e : events) { | ||
| eventIds.add(e.getEventId()); | ||
| } | ||
| return eventIds; | ||
| } |
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.
NPE 가능성: null 이벤트/ID 방어 로직 필요
events 내 null 요소나 e.getEventId()가 null이면 NPE가 발생합니다. 아래처럼 방어 로직을 추가하세요.
- for (Event e : events) {
- eventIds.add(e.getEventId());
- }
+ for (Event e : events) {
+ if (e != null && e.getEventId() != null) {
+ eventIds.add(e.getEventId());
+ }
+ }📝 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.
| private List<Long> extractEventIds(final List<Event> events) { | |
| final List<Long> eventIds = new ArrayList<>(); | |
| for (Event e : events) { | |
| eventIds.add(e.getEventId()); | |
| } | |
| return eventIds; | |
| } | |
| private List<Long> extractEventIds(final List<Event> events) { | |
| final List<Long> eventIds = new ArrayList<>(); | |
| for (Event e : events) { | |
| if (e != null && e.getEventId() != null) { | |
| eventIds.add(e.getEventId()); | |
| } | |
| } | |
| return eventIds; | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java
around lines 78 to 84, the helper method extractEventIds assumes the events list
and each Event/eventId are non-null; add defensive checks to avoid NPEs by:
handle a null events list (return empty list), skip null Event elements, and
skip or safely map null event.getEventId() values (either omit them or decide on
a sentinel/Optional) before adding to eventIds; ensure the method remains
non-null returning and document the behavior.
| //타입단위로 팔린 티켓 개수 구함 | ||
| private void computeSoldByEvent(final List<TicketType> ticketTypes, | ||
| final Map<Long, Long> roundToEvent, | ||
| final Map<Long, Integer> soldByEventId) { | ||
| for (TicketType ticketType : ticketTypes) { | ||
| final long eventId = roundToEvent.get(ticketType.getTicketRoundId()); | ||
| final int total = Math.max(0, ticketType.getTotalTicketCount()); | ||
| final int remain = Math.max(0, ticketType.getRemainTicketCount()); | ||
| final int sold = Math.max(0, total - remain); | ||
| soldByEventId.merge(eventId, sold, Integer::sum); | ||
| } | ||
| } |
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.
round → event 매핑 누락 시 NPE 위험
roundToEvent.get(...)가 null이면 long 오토언박싱에서 NPE가 납니다. 매핑이 누락된 타입은 안전하게 스킵하세요. (로그 추가도 고려)
- for (TicketType ticketType : ticketTypes) {
- final long eventId = roundToEvent.get(ticketType.getTicketRoundId());
+ for (TicketType ticketType : ticketTypes) {
+ final Long eventId = roundToEvent.get(ticketType.getTicketRoundId());
+ if (eventId == null) {
+ // 데이터 정합성 이슈: 해당 타입의 라운드가 이벤트 집합에 포함되지 않음
+ continue;
+ }
final int total = Math.max(0, ticketType.getTotalTicketCount());
final int remain = Math.max(0, ticketType.getRemainTicketCount());
final int sold = Math.max(0, total - remain);
soldByEventId.merge(eventId, sold, Integer::sum);
}📝 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.
| //타입단위로 팔린 티켓 개수 구함 | |
| private void computeSoldByEvent(final List<TicketType> ticketTypes, | |
| final Map<Long, Long> roundToEvent, | |
| final Map<Long, Integer> soldByEventId) { | |
| for (TicketType ticketType : ticketTypes) { | |
| final long eventId = roundToEvent.get(ticketType.getTicketRoundId()); | |
| final int total = Math.max(0, ticketType.getTotalTicketCount()); | |
| final int remain = Math.max(0, ticketType.getRemainTicketCount()); | |
| final int sold = Math.max(0, total - remain); | |
| soldByEventId.merge(eventId, sold, Integer::sum); | |
| } | |
| } | |
| // 타입단위로 팔린 티켓 개수 구함 | |
| private void computeSoldByEvent(final List<TicketType> ticketTypes, | |
| final Map<Long, Long> roundToEvent, | |
| final Map<Long, Integer> soldByEventId) { | |
| for (TicketType ticketType : ticketTypes) { | |
| final Long eventId = roundToEvent.get(ticketType.getTicketRoundId()); | |
| if (eventId == null) { | |
| // 데이터 정합성 이슈: 해당 타입의 라운드가 이벤트 집합에 포함되지 않음 | |
| continue; | |
| } | |
| final int total = Math.max(0, ticketType.getTotalTicketCount()); | |
| final int remain = Math.max(0, ticketType.getRemainTicketCount()); | |
| final int sold = Math.max(0, total - remain); | |
| soldByEventId.merge(eventId, sold, Integer::sum); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java
around lines 103 to 114, the call to
roundToEvent.get(ticketType.getTicketRoundId()) can return null and cause an NPE
during autounboxing; guard against a missing mapping by first fetching into a
Long (or checking for null), skip the ticketType when the mapping is absent
(optionally log a warning with the round id for diagnostics), and only proceed
to compute sold and merge into soldByEventId when the eventId is non-null.
| //최신순 정렬 | ||
| private List<Event> sortEventsByStartDateDesc(final List<Event> events) { | ||
| final List<Event> sorted = new ArrayList<>(events); | ||
| sorted.sort(Comparator.comparing(Event::getStartDate).reversed()); | ||
| return sorted; | ||
| } |
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.
정렬 Comparator의 null-세이프 보강
getStartDate()가 null인 이벤트가 섞이면 현재 Comparator에서 NPE가 발생합니다. null을 뒤로 보내면서 내림차순 정렬하도록 수정하세요.
- sorted.sort(Comparator.comparing(Event::getStartDate).reversed());
+ sorted.sort(Comparator.comparing(
+ Event::getStartDate,
+ Comparator.nullsLast(Comparator.reverseOrder())
+ ));📝 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.
| //최신순 정렬 | |
| private List<Event> sortEventsByStartDateDesc(final List<Event> events) { | |
| final List<Event> sorted = new ArrayList<>(events); | |
| sorted.sort(Comparator.comparing(Event::getStartDate).reversed()); | |
| return sorted; | |
| } | |
| //최신순 정렬 | |
| private List<Event> sortEventsByStartDateDesc(final List<Event> events) { | |
| final List<Event> sorted = new ArrayList<>(events); | |
| sorted.sort(Comparator.comparing( | |
| Event::getStartDate, | |
| Comparator.nullsLast(Comparator.reverseOrder()) | |
| )); | |
| return sorted; | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java
around lines 116 to 121, the current Comparator can throw NPE when
Event.getStartDate() is null; change the sort to use a null-safe comparator that
sends nulls to the end while keeping descending order, e.g. use
Comparator.comparing(Event::getStartDate,
Comparator.nullsLast(Comparator.reverseOrder())) when calling sorted.sort so
null startDate values appear last and non-null dates are sorted newest-first.
🔥Pull requests
⛳️ 작업한 브랜치
👷 작업한 내용
🚨 참고 사항