Skip to content

Conversation

@sjk4618
Copy link
Member

@sjk4618 sjk4618 commented Sep 5, 2025

🔥Pull requests

⛳️ 작업한 브랜치

👷 작업한 내용

  • admin 행사 리스트 조회 api 구현

🚨 참고 사항

@sjk4618 sjk4618 self-assigned this Sep 5, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 5, 2025

📝 Walkthrough

Summary by CodeRabbit

  • 신기능

    • 관리자용 이벤트 목록 조회 API를 추가했습니다. 최신 시작일 순으로 정렬되며, 연-월 단위로 묶인 섹션별로 이벤트명, 장소, 요일/일자, 판매 티켓 수가 제공됩니다.
  • 작업

    • 서비스 로그를 JSON 포맷으로 정리하고 롤링/보관 정책을 개선했습니다(시간대, 용량, 보관 기간 설정).

Walkthrough

Admin 이벤트 리스트 조회 API가 추가되었다. 컨트롤러, 서비스, DTO가 신설되며 이벤트/티켓 라운드/티켓 타입 조회 컴포넌트와 저장소 메서드가 추가·정리되었다. 날짜 유틸에 포맷터가 추가되었다. AdminExceptionHandler 패키지가 변경되었고, dev 로그백 설정이 교체되었다.

Changes

Cohort / File(s) Change Summary
Admin Event API
src/main/java/.../admin/event/api/controller/AdminEventController.java, src/main/java/.../admin/event/api/service/AdminEventService.java, src/main/java/.../admin/event/api/dto/res/EventListResponse.java
신규 관리자 이벤트 목록 API 추가: GET /api/admin/events. 서비스에서 이벤트를 조회·집계·정렬·월별 그룹화하여 DTO로 응답.
Admin Data Retrievers
src/main/java/.../admin/event/core/component/AdminEventRetriever.java, src/main/java/.../admin/ticketround/core/AdminTicketRoundRetriever.java, src/main/java/.../admin/tickettype/core/component/AdminTicketTypeRetriever.java
이벤트, 티켓 라운드, 티켓 타입 조회 컴포넌트 신설. 저장소에서 조회 후 도메인으로 매핑.
Repository updates
src/main/java/.../ticketround/core/repository/TicketRoundRepository.java, src/main/java/.../tickettype/core/repository/TicketTypeRepository.java
TicketRoundRepositoryfindByEventIdIn(...) 추가. TicketTypeRepository에서 findByTicketRoundId(long) 제거, findByTicketRoundIdIn(List<Long>) 사용 전제로 정리.
Date utilities
src/main/java/.../global/util/DateFormatterUtil.java
formatDayWithDate, formatYearMonth 포맷터 추가.
Exception handler relocation
src/main/java/.../admin/base/AdminExceptionHandler.java
패키지 경로를 ...domain.admin...domain.admin.base로 변경.
Logging config
src/main/resources/logback/logback-dev-info.xml
DEV INFO appender를 JSON 인코더 기반 설정으로 변경하고 사이즈/시간 롤링 정책 지정.

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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
관리자 행사 리스트 조회 API 구현 (#113)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
예외 처리기 패키지 경로 변경 (src/main/java/com/permitseoul/permitserver/domain/admin/base/AdminExceptionHandler.java, entire file) 행사 리스트 API 구현과 직접적 연관이 없다. 기능 동작에는 영향 없으며 범위 확장을 의미.
로그백 DEV INFO 설정을 JSON/롤링 정책으로 변경 (src/main/resources/logback/logback-dev-info.xml, entire file) 로깅 구성 변경은 API 구현 요구사항에 포함되지 않았다. 운영/관찰성 관련 별도 범위.
findByTicketRoundId(long) 제거 (src/main/java/.../tickettype/core/repository/TicketTypeRepository.java, entire file) 단건 조회 메서드 제거는 리포지토리 정리로 보이며, 명시된 API 구현 요구의 직접 항목은 아님. 다만 새 흐름과 정합성은 있으나 범위 외 가능성.

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#113

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 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.

📥 Commits

Reviewing files that changed from the base of the PR and between fa83319 and d1f3ac4.

📒 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: 추가 확인 결과를 요청했습니다.

Comment on lines +78 to +84
private List<Long> extractEventIds(final List<Event> events) {
final List<Long> eventIds = new ArrayList<>();
for (Event e : events) {
eventIds.add(e.getEventId());
}
return eventIds;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +103 to +114
//타입단위로 팔린 티켓 개수 구함
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);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
//타입단위로 팔린 티켓 개수 구함
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.

Comment on lines +116 to +121
//최신순 정렬
private List<Event> sortEventsByStartDateDesc(final List<Event> events) {
final List<Event> sorted = new ArrayList<>(events);
sorted.sort(Comparator.comparing(Event::getStartDate).reversed());
return sorted;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

정렬 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.

Suggested change
//최신순 정렬
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.

@sjk4618 sjk4618 merged commit 399516b into dev Sep 5, 2025
2 checks passed
@sjk4618 sjk4618 deleted the feat/#113 branch September 5, 2025 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: admin 행사 리스트 조회 api 구현

2 participants