Skip to content

Conversation

@sjk4618
Copy link
Member

@sjk4618 sjk4618 commented Sep 10, 2025

🔥Pull requests

⛳️ 작업한 브랜치

👷 작업한 내용

  • 행사 수정 api 구현

🚨 참고 사항

  • 바뀐 필드만 보내면 됩니다.

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

coderabbitai bot commented Sep 10, 2025

📝 Walkthrough

Summary by CodeRabbit

  • 신기능

    • 관리자 이벤트 수정 API(PATCH /api/admin/events) 추가로 이벤트 정보와 노출 기간을 업데이트할 수 있습니다.
  • 개선

    • 이벤트·티켓·타임테이블 전반의 날짜/시간 필드를 StartAt/EndAt 체계로 통일해 표시와 정렬이 더 정확해졌습니다.
    • 주문/결제 화면의 행사 일시 및 티켓 유효기간 표시가 개선되었습니다.
    • 취소/확인 과정의 날짜 파싱 로직을 정비해 안정성을 높였습니다.
    • 쿠폰 사용 시각과 예약의 결제 응답 시각 정보가 노출됩니다.
  • 버그 수정

    • 잘못된 날짜/시간 입력 시 명확한 오류 응답을 제공해 저장 실패 원인을 알기 쉽습니다.

Walkthrough

관리자용 이벤트 수정 PATCH 엔드포인트와 AdminEventUpdateRequest DTO 및 서비스/업데이트 컴포넌트를 추가하고, 이벤트·타임테이블·티켓·쿠폰·예약 도메인 전반의 날짜/시간 필드명을 startDate/endDate → startAt/endAt로 일관되게 변경했습니다.

Changes

Cohort / File(s) Summary
Admin 이벤트 수정 API
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/controller/AdminEventController.java, src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java, src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java, src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java, src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java
PATCH /api/admin/events 엔드포인트·DTO 추가, AdminEventService.updateEvent 추가, EventEntity 직접 조회 메서드 및 AdminEventUpdater 컴포넌트 도입.
Event 엔티티 및 저장소 변경
src/main/java/.../domain/event/core/domain/entity/EventEntity.java, src/main/java/.../domain/event/core/domain/Event.java, src/main/java/.../domain/event/core/repository/EventRepository.java, src/main/java/.../domain/event/core/exception/EventIllegalArgumentException.java
이벤트 엔티티·도메인 필드명 startAt/endAt·visibleStartAt/visibleEndAt로 변경, updateEvent 메서드·검증 추가, 관련 예외 타입 추가 및 쿼리 수정.
전역 유틸/예외/에러코드
src/main/java/.../global/util/DateFormatterUtil.java, src/main/java/.../global/exception/DateFormatException.java, src/main/java/.../global/response/code/ErrorCode.java
combine → combineDateAndTime 리네임, combineDateAndTimeForUpdate 추가, DateFormatException 무상태화(생성자/필드 제거), BAD_REQUEST_DATE_TIME_ERROR 추가·메시지 수정.
타임테이블 변경
src/main/java/.../eventtimetable/timetable/core/domain/entity/TimetableEntity.java, src/main/java/.../eventtimetable/timetable/core/domain/Timetable.java, src/main/java/.../eventtimetable/block/core/domain/entity/TimetableBlockEntity.java, src/main/java/.../eventtimetable/block/core/domain/TimetableBlock.java, src/main/java/.../eventtimetable/timetable/api/service/TimetableService.java, src/main/java/.../eventtimetable/block/core/exception/TimeTableIllegalArgumentException.java
timetable 관련 start/end 명칭을 startAt/endAt로 변경, 생성/팩토리/검증 로직 추가, 블록 정렬·응답 매핑 갱신, TimeTableIllegalArgumentException 추가.
티켓·티켓라운드 변경
src/main/java/.../ticket/api/service/TicketService.java, src/main/java/.../tickettype/core/domain/TicketType.java, src/main/java/.../tickettype/core/domain/entity/TicketTypeEntity.java, src/main/java/.../ticketround/core/component/TicketRoundRetriever.java, src/main/java/.../ticketround/core/domain/TicketRound.java, src/main/java/.../ticketround/core/domain/entity/TicketRoundEntity.java, src/main/java/.../ticketround/core/repository/TicketRoundRepository.java, src/main/java/.../ticketround/core/exception/TicketRoundIllegalArgumentException.java, src/main/java/.../tickettype/core/exception/TicketTypeIllegalException.java
ticket/ticketRound/ticketType의 *_Date → *_At 명칭 변경, 생성 시 유효성 검증 추가, 리포지토리 메서드명 동기화, 관련 예외 추가.
결제·예약 변경
src/main/java/.../payment/api/service/PaymentService.java, src/main/java/.../payment/api/service/TicketReservationPaymentFacade.java, src/main/java/.../reservation/core/domain/Reservation.java, src/main/java/.../reservation/core/domain/entity/ReservationEntity.java
결제 포맷에 StartAt/EndAt 적용, DateFormatException 처리 경로 조정, Reservation에 tossPaymentResponseAt 필드·컬럼명 변경.
쿠폰 변경
src/main/java/.../coupon/core/domain/Coupon.java, src/main/java/.../coupon/core/domain/entity/CouponEntity.java
Coupon에 usedAt 필드(컬럼명 used_at) 추가 및 매핑·업데이트 시그니처 변경.
이벤트 이미지 삭제 지원
src/main/java/.../admin/eventimage/core/component/AdminEventImageRemover.java, src/main/java/.../eventimage/core/repository/EventImageRepository.java
deleteAllByEventId 리포지토리 메서드와 이를 호출하는 AdminEventImageRemover 컴포넌트 추가.
기타 예외 타입 추가
src/main/java/.../eventtimetable/block/core/exception/TimeTableIllegalArgumentException.java, src/main/java/.../ticketround/core/exception/TicketRoundIllegalArgumentException.java, src/main/java/.../tickettype/core/exception/TicketTypeIllegalException.java
각 도메인별 IllegalArgument 전용 예외 타입 추가(빈 서브클래스).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin as 관리자
  participant API as AdminEventController
  participant SVC as AdminEventService
  participant RET as AdminEventRetriever
  participant DF as DateFormatterUtil
  participant UPD as AdminEventUpdater
  participant EVT as EventEntity

  Admin->>API: PATCH /api/admin/events\n(AdminEventUpdateRequest)
  API->>SVC: updateEvent(request)
  SVC->>RET: findEventEntityById(request.eventId)
  RET-->>SVC: EventEntity
  SVC->>DF: combineDateAndTimeForUpdate(date/time, EVT.visibleStartAt)
  DF-->>SVC: visibleStartAt'
  SVC->>DF: combineDateAndTimeForUpdate(..., EVT.visibleEndAt)
  DF-->>SVC: visibleEndAt'
  SVC->>DF: combineDateAndTimeForUpdate(..., EVT.startAt)
  DF-->>SVC: startAt'
  SVC->>DF: combineDateAndTimeForUpdate(..., EVT.endAt)
  DF-->>SVC: endAt'
  SVC->>UPD: updateEvent(EVT, request, visibleStartAt', visibleEndAt', startAt', endAt')
  UPD->>EVT: EVT.updateEvent(...resolved values...)
  EVT-->>UPD: updated
  UPD-->>SVC: 완료
  SVC-->>API: 200 OK
  API-->>Admin: 응답 200 OK
  rect rgba(255,240,240,0.6)
    note right of SVC: EventIllegalArgumentException 발생 시\nBAD_REQUEST_DATE_TIME_ERROR로 매핑
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks (3 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 이 PR에는 관리자 행사 수정 API 구현과 무관한 쿠폰, 이벤트 타임테이블, 결제, 예약, 티켓, 티켓 타입 등 여러 모듈에 대한 광범위한 필드명 변경 및 도메인 리팩터링이 포함되어 있어 범위를 벗어난 변경이 있습니다. 관리자 행사 수정 API 구현에만 집중할 수 있도록 도메인 전반의 필드명 변경 및 리팩터링은 별도 PR로 분리하는 것을 권장합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 “feat: admin 행사 수정 API 구현 - #123”은 관리자 행사 수정 API 구현이라는 주요 변경사항을 간결하게 설명하며 풀 리퀘스트의 핵심 목표와 정확히 일치합니다.
Linked Issues Check ✅ Passed 풀 리퀘스트는 컨트롤러의 PATCH 엔드포인트 추가, AdminEventUpdateRequest DTO 정의, 서비스 및 도메인 업데이트 로직 구현 등으로 #123 이슈에서 요구한 관리자 행사 수정 API 개발 목표를 충족합니다.
Description Check ✅ Passed 설명에는 행사 수정 API 구현이라는 작업 내용이 명확히 언급되어 있고 변경된 필드만 전송하면 된다는 참고 사항이 포함되어 있어 코드 변경사항과 직접적으로 연관됩니다.
✨ 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/#123

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.

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: 12

🧹 Nitpick comments (34)
src/main/java/com/permitseoul/permitserver/domain/reservation/core/domain/entity/ReservationEntity.java (1)

51-51: 불필요한 null 명시 초기화 제거 제안

생성자에서 this.tossPaymentResponseAt = null;은 자바 기본값과 중복됩니다. 제거해도 동작 동일하며 노이즈 감소합니다.

-        this.tossPaymentResponseAt = null;
src/main/java/com/permitseoul/permitserver/domain/coupon/core/domain/entity/CouponEntity.java (1)

53-56: isUsed ↔ usedAt 불변식 보장

  • isUsed=false 인데 기존 usedAt 값이 남는 경우 데이터 의미가 왜곡됩니다. 토글 시 일관성 정리 로직을 권장합니다(사용 해제→usedAt null, 사용 설정→usedAt 필수/자동 세팅).

적용 예:

-    public void updateCouponUsed(final boolean isUsed, final LocalDateTime usedAt) {
-        this.used = isUsed;
-        this.usedAt = usedAt;
-    }
+    public void updateCouponUsed(final boolean isUsed, final LocalDateTime usedAt) {
+        this.used = isUsed;
+        if (isUsed) {
+            this.usedAt = (usedAt != null) ? usedAt : java.time.LocalDateTime.now();
+        } else {
+            this.usedAt = null;
+        }
+    }

(프로젝트에서 Clock 주입을 선호한다면 now() 대신 Clock 기반 주입으로 대체 권장)

src/main/java/com/permitseoul/permitserver/domain/coupon/core/domain/Coupon.java (2)

18-18: usedAt null 가능성 어노테이션 명시 권장

  • 미사용 쿠폰은 usedAt이 null일 수 있습니다. 컨슈머 쪽 NPE 예방을 위해 @nullable 표시를 권장합니다(예: org.springframework.lang.Nullable).

적용 예:

+import org.springframework.lang.Nullable;
...
-    private final LocalDateTime usedAt;
+    @Nullable
+    private final LocalDateTime usedAt;

19-19: 이름 일관성: createAt → createdAt 제안

  • 필드명이 BaseTimeEntity의 getCreatedAt()과 불일치합니다. 오타성 차이로 혼동 여지가 있어 도메인 필드를 createdAt으로 정렬하는 것을 권장합니다(후속 리팩터링).

예시:

-    private final LocalDateTime createAt;
+    private final LocalDateTime createdAt;
...
-                couponEntity.getCreatedAt()
+                couponEntity.getCreatedAt()

(참조 사이드 이펙트가 커질 수 있으므로 별 PR로 진행 권장)

Also applies to: 31-31

src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java (1)

27-27: 새 BAD_REQUEST_DATE_TIME_ERROR 정의는 타당. 메시지 표현 및 사용처 일관성 확인 권장

  • 사용자 입력 유효성 오류에 대응하기 위한 추가로 보이며 방향성 좋습니다. 다만 메시지 "잘못된 date, time입니다."는 한국어 컨벤션에 맞춰 "잘못된 날짜/시간입니다." 정도로 정리하면 더 명확합니다.
  • 또한 이 코드가 실제로 클라이언트 입력 오류(예: AdminEventUpdateRequest) 경로에서 사용되는지 확인 부탁드립니다. 현재 PaymentService.cancelPayment의 DateFormatException 매핑은 INTERNAL_ISO_DATE_ERROR(500)로 되어 있어 통일성이 떨어집니다.
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/core/domain/entity/TimetableEntity.java (1)

23-27: startDate/endDate → startAt/endAt로의 스키마 매핑 변경 OK. 마이그레이션/인덱스 확인 권장

  • JPA 컬럼 명도 start_at/end_at로 일치시킨 점 좋습니다. 다만 DB 마이그레이션이 선반영됐는지와 관련 인덱스(조회가 잦다면 (event_id, start_at), (event_id, end_at) 혹은 기간 조회용 인덱스) 존재 여부를 확인해 주세요.
src/main/java/com/permitseoul/permitserver/domain/event/core/repository/EventRepository.java (1)

16-17: 가시성 조건 필드 교체 LGTM. 종료 시점 null 허용 시 쿼리 보강 제안

  • visibleEndAt가 null(무기한 노출)일 수 있다면 현재 조건에서 제외됩니다. 요구사항에 따라 null 허용이라면 아래처럼 보강을 권장합니다.
-        WHERE e.visibleStartAt <= :now
-          AND e.visibleEndAt >= :now
+        WHERE e.visibleStartAt <= :now
+          AND (e.visibleEndAt IS NULL OR e.visibleEndAt >= :now)
src/main/java/com/permitseoul/permitserver/domain/event/core/exception/EventIllegalArgumentException.java (1)

1-4: 빈 예외 클래스는 최소 맥락 전달 부족 — 생성자/기본 에러코드 부여 제안

  • 업데이트 시 잘못된 인자(특히 날짜/시간) 표현용이라면 기본 ErrorCode를 연결하거나 메시지를 전달할 수 있는 생성자를 두는 편이 진단에 유리합니다.
 package com.permitseoul.permitserver.domain.event.core.exception;
 
-public class EventIllegalArgumentException extends EventCoreException {
-}
+import com.permitseoul.permitserver.global.response.code.ErrorCode;
+
+public class EventIllegalArgumentException extends EventCoreException {
+    public EventIllegalArgumentException() {
+        super(ErrorCode.BAD_REQUEST_DATE_TIME_ERROR);
+    }
+    public EventIllegalArgumentException(final ErrorCode errorCode) {
+        super(errorCode);
+    }
+    public EventIllegalArgumentException(final String message) {
+        super(ErrorCode.BAD_REQUEST_DATE_TIME_ERROR, message);
+    }
+}

(EventCoreException 시그니처에 맞춰 조정 필요)

src/main/java/com/permitseoul/permitserver/domain/payment/api/service/TicketReservationPaymentFacade.java (1)

127-128: DateFormatException로 바로 throw 시 컨텍스트 손실 — 로깅 보강 제안

  • orElseThrow(DateFormatException::new)로는 실패 원인(빈 리스트인지, 파싱 실패인지)이 로그에 남지 않습니다. 최소 로그 후 throw로 보강을 권장합니다.
-        return  DateFormatterUtil.getLatestCancelPaymentByDate(paymentCancelResponse).orElseThrow(
-                DateFormatException::new
-        );
+        return DateFormatterUtil.getLatestCancelPaymentByDate(paymentCancelResponse)
+                .orElseThrow(() -> {
+                    // 리스트 자체가 비었거나 날짜 파싱 실패로 정렬 불가
+                    // 상위에서 500/400 매핑을 구분할 수 있도록 로그 남김
+                    // (여기서는 메시지 외에 추가 정보 전달이 어려움)
+                    return new DateFormatException();
+                });
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/controller/AdminEventController.java (1)

44-51: PATCH 엔드포인트 리소스 식별자 경로 변수화 제안

  • 현재 PATCH /api/admin/events 에서 본문으로 eventId를 전달하는 형태로 보입니다. REST 관례상 PATCH /api/admin/events/{eventId}가 명확하며, 부분 변경과 멱등성 추적(감사 로그 등)에도 유리합니다. 서비스 시그니처 변경이 가능하다면 다음 형태를 고려해 주세요.
-    @PatchMapping
-    public ResponseEntity<BaseResponse<?>> updateEvent(
-            @RequestBody @Valid final AdminEventUpdateRequest adminEventUpdateRequest
-    ) {
-        adminEventService.updateEvent(adminEventUpdateRequest);
+    @PatchMapping("/{eventId}")
+    public ResponseEntity<BaseResponse<?>> updateEvent(
+            @PathVariable final long eventId,
+            @RequestBody @Valid final AdminEventUpdateRequest adminEventUpdateRequest
+    ) {
+        adminEventService.updateEvent(eventId, adminEventUpdateRequest);
         return ApiResponseUtil.success(SuccessCode.OK);
     }
  • 추가로, 부분 갱신에서 null=미변경 의미가 충돌하지 않도록 DTO에서 Optional 필드 사용 또는 명시적 "patchable" 래퍼를 사용하는 패턴도 고려 바랍니다.
src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/entity/TicketRoundEntity.java (1)

26-31: 컬럼명 변경 OK. 마이그레이션/인덱스 확인 필요

DB 컬럼(sales_start_at, sales_end_at)로의 변경은 타당합니다. Flyway/Liquibase 마이그레이션이 포함되어 있는지와 (event_id, sales_start_at) 복합 인덱스가 존재하는지 확인 부탁드립니다. 대량 조회/정렬 경로에서 도움이 됩니다.

src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (1)

32-35: 엔티티 직접 반환 시 트랜잭션 경계 주의

findEventEntityById가 @transactional(readOnly = true)로 엔티티를 반환합니다. 이후 업데이트는 상위 서비스(@transactional) 쓰기 트랜잭션 안에서 수행되는지 확인해주세요. lazy 로딩/더티체킹 이슈 예방 차원입니다.

src/main/java/com/permitseoul/permitserver/domain/ticketround/core/component/TicketRoundRetriever.java (1)

26-26: 메서드명/정렬 기준의 의도 확인

findByEventIdAndSalesStartAtLessThanEqualOrderBySalesStartAtAsc 호출로 “미래 라운드 제외(진행/종료 포함) + 시작시각 오름차순”을 충족합니다. 메서드명(findSalesOrSalesEnd...)과 동작이 일치하므로 OK입니다. 다만 종료시각 기준 필터가 필요하다면 별도 메서드가 필요합니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (1)

30-35: 컬럼명 변경 OK. 마이그레이션/인덱스 확인

start_at/end_at로의 변경 타당합니다. 관련 마이그레이션과 (timetable_id, start_at) 인덱스 여부 확인 부탁드립니다(조회/정렬 최적화).

src/main/java/com/permitseoul/permitserver/domain/ticket/api/service/TicketService.java (2)

123-131: 만료 판정의 시스템 시계 직접 호출 최소화/일관화

UI 만료 계산에서 시스템 시계를 직접 읽습니다. 테스트 용이성과 일관성을 위해 한 번 캡쳐한 now를 사용하거나 Clock 주입을 고려해 주세요.

간단 대안(중복 now 호출 제거 + 의미 명확화):

-                                final boolean expired = isTicketDateExpired(ticketType.getTicketEndAt());
+                                final LocalDateTime now = LocalDateTime.now();
+                                final boolean expired = isTicketDateExpired(ticketType.getTicketEndAt(), now);
...
-    private boolean isTicketDateExpired(final LocalDateTime endDate) {
-        return LocalDateTime.now().isAfter(endDate);
+    private boolean isTicketDateExpired(final LocalDateTime endAt, final LocalDateTime now) {
+        return now.isAfter(endAt);
     }

208-210: 라운드 판매 가능 조건의 경계 포함 여부 확인 및 중복 로직 통합 제안

현재 양끝 포함(START/END 포함)입니다. 엔티티(TicketRoundEntity.verifyTicketSalesAvailable)와 규칙이 달라지지 않도록 도메인 측 단일 규칙으로 통합하는 것을 권장합니다(중복 로직 감소).

src/main/java/com/permitseoul/permitserver/domain/ticketround/core/repository/TicketRoundRepository.java (2)

15-16: 주석 용어 최신화

주석의 salesStartDate → salesStartAt로 정정하면 혼동을 줄일 수 있습니다.

-    // 미래 라운드는 제외: salesStartDate <= now 인 라운드만(진행 중 + 종료된 라운드)
+    // 미래 라운드는 제외: salesStartAt <= now 인 라운드만(진행 중 + 종료된 라운드)

15-16: 조회 패턴에 맞춘 인덱스 권장

자주 사용하는 조건/정렬 (event_id, sales_start_at ASC)에 맞춰 복합 인덱스를 권장합니다.

src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java (3)

11-16: 트랜잭션 경계는 Service 레이어로 올리는 것이 더 일관적입니다

Component 내부 메서드에 @transactional을 두면 서비스 단의 트랜잭션 전략과 충돌/중첩될 수 있습니다. 서비스에서 트랜잭션을 시작하고, Updater는 순수 도메인 조립만 담당하도록 분리하는 것을 권장합니다.


22-29: 부분 업데이트 시 문자열의 “빈값(Blank)” 처리 정책 명확화 필요

현재는 null만 “미변경”으로 간주합니다. 빈 문자열("")이 들어오면 해당 필드가 비워지는 동작인데, PR 설명(“바뀐 필드만 보내면 됩니다”)을 따르면 보통 “미전달(null)만 미변경”이고 빈 문자열은 의도치 않은 초기화가 될 수 있습니다. “미전달(null)=미변경, 공란 허용 X”라면 hasText 기준으로 변경을 권장합니다. 또한 verificationCode와 엔티티의 ticketCheckCode 네이밍 상이로 인한 혼선을 줄이기 위해 명칭 통일을 고려해 주세요.

아래와 같이 변경 시 빈 문자열을 무시(미변경)합니다.

-        final String eventName = eventUpdateRequest.name() == null ? eventEntity.getName() : eventUpdateRequest.name();
+        final String eventName = org.springframework.util.StringUtils.hasText(eventUpdateRequest.name()) ? eventUpdateRequest.name() : eventEntity.getName();
-        final String venue = eventUpdateRequest.venue() == null ? eventEntity.getVenue() : eventUpdateRequest.venue();
+        final String venue = org.springframework.util.StringUtils.hasText(eventUpdateRequest.venue()) ? eventUpdateRequest.venue() : eventEntity.getVenue();
-        final String lineUp = eventUpdateRequest.lineup() == null ? eventEntity.getLineUp() : eventUpdateRequest.lineup();
+        final String lineUp = org.springframework.util.StringUtils.hasText(eventUpdateRequest.lineup()) ? eventUpdateRequest.lineup() : eventEntity.getLineUp();
-        final String details = eventUpdateRequest.details() == null ? eventEntity.getDetails() : eventUpdateRequest.details();
+        final String details = org.springframework.util.StringUtils.hasText(eventUpdateRequest.details()) ? eventUpdateRequest.details() : eventEntity.getDetails();
-        final String verificationCode = eventUpdateRequest.verificationCode() == null ? eventEntity.getTicketCheckCode() : eventUpdateRequest.verificationCode();
+        final String verificationCode = org.springframework.util.StringUtils.hasText(eventUpdateRequest.verificationCode()) ? eventUpdateRequest.verificationCode() : eventEntity.getTicketCheckCode();

추가로, minAge에 대한 하한/상한(예: @min(0)) 유효성 검증이 DTO에 있는지 확인 부탁드립니다.


30-31: updateEvent 인자 개수가 많아 오인자/순서 오류 리스크가 큽니다

파라미터 오더가 바뀌어도 컴파일 타임에 잡히지 않습니다. Command/Builder(예: EventUpdateCommand)로 묶거나, EventEntity의 업데이트 메서드를 명시적 세터/플루언트 API로 분해하는 것을 권장합니다. 또한 startAt ≤ endAt, visibleStartAt ≤ visibleEndAt 검증이 상위(Service)에서 이뤄지는지 확인해 주세요.

원하시면 Command 레코드/Builder 초안을 드리겠습니다.

src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1)

98-102: blank canceledAt 방어 로직 추가 제안

현재 null만 필터링합니다. blank 문자열이 들어오면 parseTossDateToLocalDateTime에서 예외가 납니다. blank도 제외해 주세요.

-        return cancels.stream()
-                .filter(cancel -> cancel.canceledAt() != null)
-                .max(Comparator.comparing(cancel -> parseTossDateToLocalDateTime(cancel.canceledAt())));
+        return cancels.stream()
+                .filter(cancel -> cancel.canceledAt() != null && !cancel.canceledAt().isBlank())
+                .max(Comparator.comparing(cancel -> parseTossDateToLocalDateTime(cancel.canceledAt())));
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/service/TimetableService.java (1)

156-158: 정렬 안정성 및 null 안전성 보강 제안

getStartAt()가 null인 데이터가 들어오면 NPE가 발생하고, 동일 시작 시각 간 순서가 비결정적일 수 있습니다. null을 뒤로 보내고, tie-breaker를 추가하면 안정적입니다.

-        return blockList.stream()
-                .sorted(Comparator.comparing(TimetableBlock::getStartAt))
+        return blockList.stream()
+                .sorted(
+                        Comparator.comparing(
+                                TimetableBlock::getStartAt,
+                                Comparator.nullsLast(Comparator.naturalOrder())
+                        ).thenComparing(TimetableBlock::getTimetableBlockId)
+                )
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/TicketType.java (1)

30-31: 도메인 불변식 검증 추가 제안 (start ≤ end, null 금지)
fromEntity 호출 시 ticketStartAt/ticketEndAt에 대해 null 및 순서 검증을 위해 아래 헬퍼 메서드 사용을 권장합니다.

-                ticketTypeEntity.getTicketStartAt(),
-                ticketTypeEntity.getTicketEndAt()
+                validateStart(ticketTypeEntity.getTicketStartAt()),
+                validateEnd(ticketTypeEntity.getTicketStartAt(), ticketTypeEntity.getTicketEndAt())
private static LocalDateTime validateStart(LocalDateTime startAt) {
    return Objects.requireNonNull(startAt, "ticketStartAt must not be null");
}
private static LocalDateTime validateEnd(LocalDateTime startAt, LocalDateTime endAt) {
    Objects.requireNonNull(endAt, "ticketEndAt must not be null");
    if (endAt.isBefore(startAt)) {
        throw new IllegalArgumentException("ticketEndAt must be on/after ticketStartAt");
    }
    return endAt;
}

레거시 접근자(getTicketStartDate/getTicketEndDate)는 이미 제거된 상태입니다.

src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (4)

139-144: 노출/이벤트 시간 상호 제약 검토

현재는 (startAt ≤ endAt), (visibleStartAt ≤ visibleEndAt)만 검증하는 것으로 보입니다. 비즈니스 규칙상 “노출 구간이 이벤트 구간을 완전히 포함/선행해야 한다” 등 추가 제약이 있다면 여기서도 교차 검증(예: visibleEndAt ≥ startAt 등)을 고려해 주세요.


146-149: 예외 원인 보존 또는 로깅 권장

원인(Exception cause) 전달이 가능하다면 AdminApiException에 cause를 함께 전달하거나 경고 로그를 남겨 디버깅 가시성을 높여 주세요.


159-165: 이미지 URL null/blank 방어 및 시퀀스 재부여

imageUrl에 trim을 바로 적용하면 NPE 가능성이 있습니다. null/blank를 필터링하고 유효 항목에 1부터 다시 시퀀스를 부여하는 편이 안전합니다.

아래 패치를 제안합니다(외부 import 추가 필요).

-        final List<EventImageEntity> eventImageEntityList = IntStream.range(0, eventImages.size())
-                .mapToObj(i -> EventImageEntity.create(
-                        eventId,
-                        eventImages.get(i).imageUrl().trim(),
-                        i+1
-                ))
-                .toList();
+        final java.util.concurrent.atomic.AtomicInteger seq = new java.util.concurrent.atomic.AtomicInteger(1);
+        final List<EventImageEntity> eventImageEntityList = eventImages.stream()
+                .filter(Objects::nonNull)
+                .map(AdminEventWithTicketCreateRequest.AdminEventImageInfo::imageUrl)
+                .filter(Objects::nonNull)
+                .map(String::trim)
+                .filter(s -> !s.isBlank())
+                .map(url -> EventImageEntity.create(eventId, url, seq.getAndIncrement()))
+                .toList();

추가: 필요 시 파일 상단에 아래 import를 넣어 주세요.

import java.util.concurrent.atomic.AtomicInteger;

169-178: 티켓 타입의 판매 기간 결합 로직 일관화

생성 경로에서도 DateFormatterUtil을 사용 중이므로, 여기서도 LocalDateTime.of 대신 유틸을 사용해 검증/예외 처리를 일원화하는 것을 권장합니다.

-                        LocalDateTime.of(ticketType.ticketStartDate(), ticketType.ticketStartTime()),
-                        LocalDateTime.of(ticketType.ticketEndDate(), ticketType.ticketEndTime())
+                        DateFormatterUtil.combineDateAndTime(ticketType.ticketStartDate(), ticketType.ticketStartTime()),
+                        DateFormatterUtil.combineDateAndTime(ticketType.ticketEndDate(), ticketType.ticketEndTime())
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (3)

57-67: create 메서드 네이밍/검증 일관화

  • 파라미터명이 startDate/endDate로 남아 있어 가독성이 떨어집니다. startAt/endAt으로 변경 권장.
  • 생성 시점에도 기간 검증을 호출해 일관성 유지하는 것이 좋습니다.
-    public static EventEntity create(final String name,
-                                     final EventType eventType,
-                                     final LocalDateTime startDate,
-                                     final LocalDateTime endDate,
+    public static EventEntity create(final String name,
+                                     final EventType eventType,
+                                     final LocalDateTime startAt,
+                                     final LocalDateTime endAt,
                                      final String venue,
                                      final String lineUp,
                                      final String details,
                                      final int minAge,
                                      final LocalDateTime visibleStartAt,
                                      final LocalDateTime visibleEndAt,
                                      final String ticketCheckCode) {
-        return EventEntity.builder()
+        validateDate(startAt, endAt);
+        validateDate(visibleStartAt, visibleEndAt);
+        return EventEntity.builder()
                 .name(name)
                 .eventType(eventType)
-                .startAt(startDate)
-                .endAt(endDate)
+                .startAt(startAt)
+                .endAt(endAt)
                 .venue(venue)
                 .lineUp(lineUp)
                 .details(details)
                 .minAge(minAge)
                 .visibleStartAt(visibleStartAt)
                 .visibleEndAt(visibleEndAt)
                 .ticketCheckCode(ticketCheckCode)
                 .build();
     }

Also applies to: 71-79


83-108: updateEvent 입력 방어(널) 강화 제안

DB 제약은 nullable=false이지만 메서드 진입부에서 핵심 시간 필드를 명시적으로 null 체크하면 도메인 일관성이 높아집니다.

     public void updateEvent(final String name,
                             final EventType eventType,
                             final LocalDateTime startAt,
                             final LocalDateTime endAt,
                             final String venue,
                             final String lineUp,
                             final String details,
                             final int minAge,
                             final LocalDateTime visibleStartAt,
                             final LocalDateTime visibleEndAt,
                             final String ticketCheckCode) {
+        Objects.requireNonNull(startAt, "startAt");
+        Objects.requireNonNull(endAt, "endAt");
+        Objects.requireNonNull(visibleStartAt, "visibleStartAt");
+        Objects.requireNonNull(visibleEndAt, "visibleEndAt");
         validateDate(startAt, endAt);
         validateDate(visibleStartAt, visibleEndAt);

추가: 필요 시 파일 상단에 아래 import를 넣어 주세요.

import java.util.Objects;

110-115: 동일 시각 허용 여부 명시 필요

현재는 end가 start “이전”일 때만 예외입니다(동일 시각 허용). 비즈니스 규칙상 endAt == startAt을 금지해야 한다면 isBefore 대신 !isAfter 등으로 조정이 필요합니다.

src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java (2)

17-28: 날짜·시간 범위의 유효성 보장 필요(start ≤ end)

부분 업데이트(PATCH) 특성상 둘 다 전달된 경우에 한해 다음을 검증하는 클래스/서비스 레벨 체크가 필요합니다:

  • 노출: (eventExposureStartDate+Time) ≤ (eventExposureEndDate+Time)
  • 본행사: (startDate+Time) ≤ (endDate+Time)

서비스에서 병합 후 검증 예시(개념 코드):

// 병합된 LocalDateTime visibleStartAt, visibleEndAt, startAt, endAt 기준
if (visibleStartAt != null && visibleEndAt != null && visibleStartAt.isAfter(visibleEndAt)) {
    throw new IllegalArgumentException("노출 시작이 종료보다 늦습니다.");
}
if (startAt != null && endAt != null && startAt.isAfter(endAt)) {
    throw new IllegalArgumentException("행사 시작이 종료보다 늦습니다.");
}

또는 Bean Validation 커스텀 클래스 레벨 제약으로도 가능합니다.

Also applies to: 34-45


31-31: AdminEventUpdateRequest에 문자열 길이 제약 추가

  • name, venue, lineUp 필드에 DB 기본 VARCHAR(255) 또는 실제 스키마 길이에 맞춰 @Size(max=255) 적용
  • details는 TEXT 타입이므로 DB 예외 위험은 없으나, 정책 일관성 차원에서 필요 시 @Size(max=…) 적용
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (1)

61-64: 도메인 불변식 가드: ticketStartAt ≤ ticketEndAt

엔티티 팩토리에서 시간 역전 입력을 조기에 차단해 두면 이후 버그를 줄일 수 있습니다.

 public static TicketTypeEntity create(final long ticketRoundId,
                                       final String ticketTypeName,
                                       final BigDecimal ticketPrice,
                                       final int totalTicketCount,
                                       final LocalDateTime ticketStartAt,
                                       final LocalDateTime ticketEndAt) {
+        if (ticketStartAt.isAfter(ticketEndAt)) {
+            throw new IllegalArgumentException("ticketStartAt은 ticketEndAt보다 늦을 수 없습니다.");
+        }
         return new TicketTypeEntity(ticketRoundId, ticketTypeName, ticketPrice, totalTicketCount, ticketStartAt, ticketEndAt);
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e160e01 and b1eec8f.

📒 Files selected for processing (31)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/controller/AdminEventController.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (8 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/coupon/core/domain/Coupon.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/coupon/core/domain/entity/CouponEntity.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/event/api/service/EventService.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/event/core/domain/Event.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (4 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/event/core/exception/EventIllegalArgumentException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/event/core/repository/EventRepository.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/TimetableBlock.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (4 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/service/TimetableService.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/core/domain/Timetable.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/core/domain/entity/TimetableEntity.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/payment/api/service/TicketReservationPaymentFacade.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/reservation/core/domain/Reservation.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/reservation/core/domain/entity/ReservationEntity.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticket/api/service/TicketService.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticketround/core/component/TicketRoundRetriever.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/TicketRound.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/entity/TicketRoundEntity.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticketround/core/repository/TicketRoundRepository.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/TicketType.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/exception/DateFormatException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java (1)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (1)
  • Component (13-36)
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)
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java (1)
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventWithTicketCreateRequest.java (1)
  • AdminEventWithTicketCreateRequest (13-125)
src/main/java/com/permitseoul/permitserver/domain/payment/api/service/TicketReservationPaymentFacade.java (1)
src/main/java/com/permitseoul/permitserver/global/exception/DateFormatException.java (1)
  • DateFormatException (4-4)
src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java (1)
src/main/java/com/permitseoul/permitserver/global/exception/DateFormatException.java (1)
  • DateFormatException (4-4)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventRetriever.java (1)
src/main/java/com/permitseoul/permitserver/domain/admin/event/core/exception/AdminEventNotFoundException.java (1)
  • AdminEventNotFoundException (3-4)
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/exception/EventIllegalArgumentException.java (1)
  • EventIllegalArgumentException (3-4)
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/exception/EventIllegalArgumentException.java (1)
  • EventIllegalArgumentException (3-4)
🔇 Additional comments (27)
src/main/java/com/permitseoul/permitserver/domain/reservation/core/domain/Reservation.java (2)

30-31: fromEntity 매핑 일관성/LGTM

엔티티의 getTossPaymentResponseAt()을 도메인으로 정확히 전달하고 있습니다. 변경된 네이밍과도 일치합니다.


8-20: 타임존 및 널 처리 정책 명시 필요
Reservation.tossPaymentResponseAt는 시간대 정보가 없는 LocalDateTime이며 null 허용 여부도 명시되어 있지 않습니다. API/DB 경계에서 UTC 고정(OffsetDateTime 또는 Instant 사용), Jackson JavaTimeModule 등록 및 WRITE_DATES_AS_TIMESTAMPS 비활성화, @Nullable/@NotNull 어노테이션 등으로 계약을 명확히 표시하세요.

src/main/java/com/permitseoul/permitserver/domain/coupon/core/domain/Coupon.java (1)

29-31: fromEntity 매핑 LGTM

  • 엔티티 usedAt → 도메인 usedAt 매핑 일관성 좋습니다.
src/main/java/com/permitseoul/permitserver/domain/event/api/service/EventService.java (1)

51-52: StartAt/EndAt 기반 포맷 변경 LGTM

  • DateFormatterUtil/TimeFormatterUtil 호출부가 새 필드에 맞게 수정되어 일관성 좋습니다.
src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java (1)

152-153: StartAt/EndAt 사용으로 날짜 포맷 일관화 LGTM

src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/entity/TicketRoundEntity.java (2)

41-44: 팩토리 메서드 시그니처/연결 정상

도메인 생성 경로가 명확하고 일관적입니다.


46-49: 경계 포함 여부 비즈니스 규칙 확인

현재 now == salesEndAt 시 “판매 가능”으로 처리됩니다(종료 시각 포함). 의도된 규칙인지 확인 부탁드립니다. 불포함이 의도라면 isAfter → isEqual/isAfter 조합으로 조정이 필요합니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/TimetableBlock.java (1)

16-17: 네이밍 일관성 반영 완료

startAt/endAt으로의 전환 및 매핑(fromEntity) 일관성 확인했습니다.

Also applies to: 30-31

src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/TicketRound.java (1)

15-16: 필드/매핑 변경 LGTM

salesStartAt/salesEndAt으로의 변경과 fromEntity 매핑이 일치합니다.

Also applies to: 23-24

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (1)

79-87: 팩토리 메서드 연결 정상

create(...) → 생성자 전달이 정확합니다.

src/main/java/com/permitseoul/permitserver/domain/ticket/api/service/TicketService.java (1)

245-247: 시간 표시 사양 확인

티켓 타입 표시에서는 시작 시각만(HH:mm) 노출합니다. 상단 TicketInfo는 구간(start~end) 포맷을 사용 중이라 UI 사양 차이인지 확인 필요합니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/core/domain/Timetable.java (2)

14-15: StartDate/EndDate → StartAt/EndAt 전환 LGTM

도메인/엔티티 매핑 일관성 좋습니다.

Also applies to: 21-22


17-24: 레거시 getStartDate()/getEndDate() 호출 제거 확인됨
코드베이스 내에 더 이상 해당 메서드 호출이 존재하지 않아 참조 정리가 완료되었습니다.

src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (2)

72-77: 메서드 네이밍 명확화 LGTM

combine → combineDateAndTime로 명확해졌습니다.


91-93: 빈 문자열 처리로 예외 발생 범위가 넓어졌습니다 — 상위 호출부 확인 필요

isoDate가 blank일 때도 DateFormatException을 던지므로, 컨트롤러/서비스 단 예외 매핑과 호출부(특히 결제 취소 조회)가 이에 맞게 방어되었는지 확인 바랍니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/service/TimetableService.java (2)

57-62: StartAt/EndAt 이관 LGTM

TimetableResponse.ofstartAt/endAt 전달 변경 적절합니다. 관련 DTO 시그니처/직렬화 포맷만 호환되는지 확인해 주세요.


165-171: 블록 시간 필드 이관 LGTM (추가로 타임존/직렬화 포맷 확인 권장)

block.getStartAt()/getEndAt() 사용 전환 적절합니다. 외부 응답이 LocalDateTime 기반인 경우 서버 타임존에 종속될 수 있으니, API 응답이 기대하는 타임존/오프셋(예: ISO-8601 + offset/Z)을 사용하는지 한 번만 점검해 주세요.

src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/TicketType.java (1)

19-20: 필드 네이밍 이관 LGTM

ticketStartAt/ticketEndAt로의 네이밍 정리 일관성 좋아요.

src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (4)

87-97: 상세 조회 날짜/시간 포맷 변경 LGTM

At 필드로의 전환 및 포맷팅 일관성 좋습니다.


290-291: 정렬 키 변경 LGTM

startAt 기준 최신순 정렬 전환 적절합니다.


300-301: 그룹핑 기준 변경 LGTM

그룹핑 기준을 startAt으로 맞춘 부분 일관성 좋습니다.


132-151: 문제 없음: AdminEventUpdater.updateEvent에 @transactional 어노테이션이 이미 선언되어 있습니다
AdminEventService.updateEvent에 별도 트랜잭션 어노테이션 추가가 불필요합니다.

src/main/java/com/permitseoul/permitserver/domain/event/core/domain/Event.java (2)

15-17: **필드 명/접근자(Date → At) 전환 LGTM

도메인 모델의 시간 필드 일관성 확보에 도움이 됩니다.

Also applies to: 21-23


30-39: 매핑 순서 및 레거시 접근자 검증 완료
생성자 파라미터 순서가 필드 선언 순서와 일치하며, getStartDate/getEndDate 등 레거시 접근자는 코드베이스에 존재하지 않습니다.

src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)

30-35: DB 컬럼/필드 전환 LGTM

start_at/end_at 및 visible_start_at/visible_end_at로의 매핑 변경 적절합니다.

Also applies to: 48-52

src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java (1)

14-16: 중복 수신 이슈 없음
AdminEventController의 updateEvent 메서드는 클래스 레벨 @RequestMapping("/api/admin/events")와 메서드 레벨 @PatchMapping을 사용해 경로 변수 없이 요청 본문으로만 eventId를 전달하므로, 본문에 eventId를 유지해도 문제가 없습니다.

Likely an incorrect or invalid review comment.

src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (1)

36-40: Getter 이름 변경으로 인한 영향 확인 완료
getTicketStartDate/EndDate 호출부가 코드베이스에 존재하지 않아(getTicketStartAt/EndAt로 변경된 메서드 이름에 대한 잔여 참조 없음) 추가 조치 불필요합니다.

Comment on lines 48 to 55
List<AdminEventImageInfo> images,

@Min(value = 0, message = "최소 나이는 0 이상이어야 합니다.")
Integer minAge
) {
public record AdminEventImageInfo(
String imageUrl
) { }
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이미지 검증 누락: @Valid + @notblank 추가 권장

업데이트라 하더라도 images가 전달되면 각 항목의 유효성이 보장되어야 합니다. 현재 리스트에 @Valid가 없고, 내부 레코드의 imageUrl에도 공란 방지 검증이 없습니다. 생성 DTO와의 계약 일관성도 맞춰주세요.

아래처럼 보완을 제안합니다:

-        List<AdminEventImageInfo> images,
+        @Valid
+        List<AdminEventImageInfo> images,

     public record AdminEventImageInfo(
-            String imageUrl
+            @NotBlank(message = "이미지 url은 필수입니다.")
+            String imageUrl
     ) { }
📝 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
List<AdminEventImageInfo> images,
@Min(value = 0, message = "최소 나이는 0 이상이어야 합니다.")
Integer minAge
) {
public record AdminEventImageInfo(
String imageUrl
) { }
@Valid
List<AdminEventImageInfo> images,
@Min(value = 0, message = "최소 나이는 0 이상이어야 합니다.")
Integer minAge
) {
public record AdminEventImageInfo(
@NotBlank(message = "이미지 url은 필수입니다.")
String imageUrl
) { }
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java
around lines 48 to 55, the images list lacks nested validation and the imageUrl
field has no non-blank constraint; add @Valid to the List<AdminEventImageInfo>
images parameter and annotate the imageUrl in the AdminEventImageInfo record
with @NotBlank (and import javax.validation.Valid and
javax.validation.constraints.NotBlank) so each provided image entry is validated
consistently with the create DTO contract.

Comment on lines +37 to +38
@Column(name = "used_at")
private LocalDateTime usedAt;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

컬럼명 변경(used_time → used_at)에 따른 마이그레이션 및 널 허용성 명시 필요

  • DB 컬럼 rename/생성 마이그레이션이 없다면 런타임 매핑 오류가 납니다. 마이그레이션 스크립트 포함 여부 확인 부탁드립니다.
  • 사용 취소 시 usedAt이 null이 될 수 있으므로 nullable 명시를 권장합니다.

적용 예:

-    @Column(name = "used_at")
+    @Column(name = "used_at", nullable = true)
     private LocalDateTime usedAt;

마이그레이션/레거시 참조 확인 스크립트:


🏁 Script executed:

#!/bin/bash
# used_time / used_at 컬럼 및 심볼 잔존 여부 점검
rg -n -C2 -i 'used_time|used_at|usedTime|usedAt|getUsedTime|setUsedTime' || true

# (선택) 마이그레이션 위치 추정 및 내용 조회
fd -HI -t f db | sed -n '1,200p' || true
rg -n -i 'used_at|used_time' --resources --glob '*db*' || true

Length of output: 6760


#!/bin/bash

Search for Flyway/Liquibase migration scripts renaming or creating used_time/used_at columns

find src/main/resources -type f -name '*.sql' -print -exec grep -n -i 'used_time|used_at' {} ; || true


🏁 Script executed:

#!/bin/bash
# 마이그레이션 스크립트 내 used_time/used_at 검색
find src/main/resources -type f \( -iname '*.sql' -o -iname '*.yml' -o -iname '*.yaml' -o -iname '*.xml' \) \
  -exec grep -nE 'used_time|used_at' {} +

Length of output: 0


마이그레이션 스크립트 및 nullable 설정 추가 필요

  • DB 컬럼 used_time → used_at rename/creation을 다루는 마이그레이션 스크립트(src/main/resources/db/migration 등) 누락 여부 확인 후, 없으면 스크립트 추가
  • 사용 취소 시 usedAt 이 null이 될 수 있으므로
    -    @Column(name = "used_at")
    +    @Column(name = "used_at", nullable = true)
         private LocalDateTime usedAt;
    으로 nullable=true 명시 필요
📝 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
@Column(name = "used_at")
private LocalDateTime usedAt;
@Column(name = "used_at", nullable = true)
private LocalDateTime usedAt;
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/coupon/core/domain/entity/CouponEntity.java
around lines 37 to 38, the usedAt field mapping lacks nullable=true and there is
no migration handling for the DB column rename/creation (used_time → used_at);
update the entity's @Column annotation to include nullable = true and add a
Flyway/Liquibase migration script under src/main/resources/db/migration to
create or rename the column to used_at (or add it if missing) so the schema and
entity nullable semantics match and rolling back/canceling uses can set usedAt
to null.

Comment on lines +29 to 37
private TimetableEntity(long eventId, LocalDateTime startAt, LocalDateTime endAt) {
this.eventId = eventId;
this.startDate = startDate;
this.endDate = endDate;
this.startAt = startAt;
this.endAt = endAt;
}

public static TimetableEntity create(final long eventId, final LocalDateTime startDate, final LocalDateTime endDate) {
return new TimetableEntity(eventId, startDate, endDate);
public static TimetableEntity create(final long eventId, final LocalDateTime startAt, final LocalDateTime endAt) {
return new TimetableEntity(eventId, startAt, endAt);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

시간 역전 방지 등의 불변식 검증 추가 제안

  • 생성자/팩토리에서 startAt <= endAt 보장 검증이 없습니다. 데이터 무결성을 위해 최소한의 가드 추가를 권장합니다.

적용 예시:

 private TimetableEntity(long eventId, LocalDateTime startAt, LocalDateTime endAt) {
+    if (startAt == null || endAt == null) {
+        throw new IllegalArgumentException("startAt/endAt must not be null");
+    }
+    if (endAt.isBefore(startAt)) {
+        throw new IllegalArgumentException("endAt must be after or equal to startAt");
+    }
     this.eventId = eventId;
     this.startAt = startAt;
     this.endAt = endAt;
 }
📝 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 TimetableEntity(long eventId, LocalDateTime startAt, LocalDateTime endAt) {
this.eventId = eventId;
this.startDate = startDate;
this.endDate = endDate;
this.startAt = startAt;
this.endAt = endAt;
}
public static TimetableEntity create(final long eventId, final LocalDateTime startDate, final LocalDateTime endDate) {
return new TimetableEntity(eventId, startDate, endDate);
public static TimetableEntity create(final long eventId, final LocalDateTime startAt, final LocalDateTime endAt) {
return new TimetableEntity(eventId, startAt, endAt);
}
private TimetableEntity(long eventId, LocalDateTime startAt, LocalDateTime endAt) {
if (startAt == null || endAt == null) {
throw new IllegalArgumentException("startAt/endAt must not be null");
}
if (endAt.isBefore(startAt)) {
throw new IllegalArgumentException("endAt must be after or equal to startAt");
}
this.eventId = eventId;
this.startAt = startAt;
this.endAt = endAt;
}
public static TimetableEntity create(final long eventId, final LocalDateTime startAt, final LocalDateTime endAt) {
return new TimetableEntity(eventId, startAt, endAt);
}
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/core/domain/entity/TimetableEntity.java
around lines 29 to 37, the constructor/factory does not validate the temporal
invariant so startAt may be after endAt; add validation to enforce startAt !=
null, endAt != null and startAt.isBefore(endAt) or startAt.equals(endAt) as
required, and throw an IllegalArgumentException (or a domain-specific exception)
with a clear message when the check fails; perform the checks in the private
constructor (or at the start of the static create method) so any creation path
is guarded.

Comment on lines +245 to 246
throw new PaymentBadRequestException(ErrorCode.INTERNAL_ISO_DATE_ERROR);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

DateFormatException 매핑이 500/400 의미와 불일치 — 오류 구분 및 예외 타입 정리 필요

  • 현재 DateFormatException을 PaymentBadRequestException(ErrorCode.INTERNAL_ISO_DATE_ERROR)로 변환합니다. 이름상 BadRequest(400)인데 ErrorCode는 500이라 의미가 충돌합니다.
  • 이 경로는 토스 응답 날짜 파싱 실패(외부 시스템 데이터) 가능성이 높아 500 INTERNAL 계열이 더 적합합니다. 최소한 로깅을 보강하고 500용 예외 타입으로 변경을 권장합니다.
-        } catch(DateFormatException e) {
-            throw new PaymentBadRequestException(ErrorCode.INTERNAL_ISO_DATE_ERROR);
+        } catch (DateFormatException e) {
+            log.error("[PAYMENT CANCEL] Toss ISO date parse failed userId={}, orderId={}", userId, orderId, e);
+            // 외부 응답 파싱 실패 → 내부 오류(500)로 노출
+            throw new PaymentFeignException(ErrorCode.INTERNAL_ISO_DATE_ERROR,
+                    "토스 ISO 날짜 파싱 실패");
         }

추가로, 클라이언트 입력(관리자 이벤트 수정 등)에서 발생한 DateFormatException은 BAD_REQUEST_DATE_TIME_ERROR(400)로 매핑하고, 외부 응답 파싱 실패는 INTERNAL_ISO_DATE_ERROR(500)로 구분되도록 전반적 정책을 정리해 주세요.

📝 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
throw new PaymentBadRequestException(ErrorCode.INTERNAL_ISO_DATE_ERROR);
}
} catch (DateFormatException e) {
log.error("[PAYMENT CANCEL] Toss ISO date parse failed userId={}, orderId={}", userId, orderId, e);
// 외부 응답 파싱 실패 → 내부 오류(500)로 노출
throw new PaymentFeignException(
ErrorCode.INTERNAL_ISO_DATE_ERROR,
"토스 ISO 날짜 파싱 실패"
);
}
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java
around lines 245-246, the DateFormatException is currently mapped to
PaymentBadRequestException(ErrorCode.INTERNAL_ISO_DATE_ERROR) which mixes 400
semantics with a 500 code; change this path to throw the 500-level exception
type (e.g., PaymentInternalException or PaymentException with
ErrorCode.INTERNAL_ISO_DATE_ERROR) and add a clear log entry including the
exception and a marker that the failure came from external response parsing;
conversely ensure any DateFormatException caused by client input is mapped to
PaymentBadRequestException(ErrorCode.BAD_REQUEST_DATE_TIME_ERROR) with
appropriate logging indicating client-side input error; update any central
error-mapping/policy docs and unit tests to reflect these two distinct mappings.

Comment on lines +36 to +40
@Column(name = "ticket_start_at", nullable = false)
private LocalDateTime ticketStartAt;

@Column(name = "ticket_end_date", nullable = false)
private LocalDateTime ticketEndDate;
@Column(name = "ticket_end_at", nullable = false)
private LocalDateTime ticketEndAt;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

컬럼명 변경에 따른 DB 마이그레이션/쿼리 영향 점검

ticket_start_date/end_date → ticket_start_at/end_at로 변경되었습니다. 스키마 마이그레이션(컬럼 rename, not null/인덱스 유지)과 네이티브 쿼리/뷰/프로시저 참조가 있는지 확인이 필요합니다.

점검 스크립트:


🏁 Script executed:

#!/bin/bash
# 기존/신규 컬럼명 사용 흔적 검색
rg -nP -C2 'ticket_(start|end)_(date|at)' --glob '!**/build/**' --glob '!**/target/**'

# Flyway/Liquibase 마이그레이션 파일 후보 탐색
fd -t f -i 'db/migration|migrations|liquibase|flyway' 2>/dev/null || true
rg -nP -C2 'rename\s+column|alter\s+table|ticket_type' src/main/resources 2>/dev/null || true

Length of output: 148


🏁 Script executed:

#!/bin/bash
set -e

# 1. 새로운/기존 컬럼명 사용 위치 검색
echo "## 코드 내 컬럼명 사용 검색"
rg -n 'ticket_start_at|ticket_end_at|ticket_start_date|ticket_end_date' -g '*.java' -g '*.sql' -g '*.xml' --glob 'src/**' || true

# 2. 마이그레이션 디렉토리 탐색 및 파일 목록 확인
migration_dirs=(
  src/main/resources/db/migration
  src/main/resources/db/changelog
  src/main/resources/liquibase
)
for dir in "${migration_dirs[@]}"; do
  if [ -d "$dir" ]; then
    echo "## $dir 파일 목록"
    ls "$dir" || true
    echo "## $dir 내 ALTER/RENAME 문 검색"
    rg -nP 'alter\s+table|rename(Column|column)' "$dir" || true
  fi
done

Length of output: 337


마이그레이션 스크립트 누락 확인 필요 ticket_start_date→ticket_start_at, ticket_end_date→ticket_end_at 컬럼 리네임 대응으로 ALTER TABLE … RENAME COLUMN 스크립트를 src/main/resources/db/migration(또는 사용 중인 마이그레이션 디렉터리)에 추가했는지, 네이티브 쿼리·뷰·프로시저 내 해당 컬럼 참조가 없는지 검증 바랍니다.

🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java
around lines 36 to 40, the entity fields were renamed from
ticket_start_date→ticket_start_at and ticket_end_date→ticket_end_at but the
migration to apply these renames may be missing; add a migration script (e.g.,
in src/main/resources/db/migration) that runs ALTER TABLE <table_name> RENAME
COLUMN ticket_start_date TO ticket_start_at; and ALTER TABLE <table_name> RENAME
COLUMN ticket_end_date TO ticket_end_at; (use your actual table name and proper
up/down if applicable), then search the codebase and database objects (native
queries, SQL files, views, stored procedures, triggers) for any remaining
references to ticket_start_date or ticket_end_date and update them to the new
column names, and run the migration in a test environment to verify no runtime
SQL errors occur.

this.errorCode = errorCode;
}
}
public class DateFormatException extends PermitGlobalException { }
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

상위 클래스 생성자 요구사항 확인 및 기본 ErrorCode 연결 제안

기본 생성자만 두면 super()가 호출됩니다. PermitGlobalException에 무인자 생성자가 없거나(혹은 ErrorCode를 반드시 받도록 강제) 전역 핸들러가 ErrorCode 기반으로 응답을 구성한다면, 현재 구현은 컴파일/런타임에서 깨질 수 있습니다. 안전하게 기본 에러코드를 연결하는 편이 좋습니다.

적용 제안(diff):

-package com.permitseoul.permitserver.global.exception;
-
-
-public class DateFormatException extends PermitGlobalException { }
+package com.permitseoul.permitserver.global.exception;
+
+import com.permitseoul.permitserver.global.response.code.ErrorCode;
+
+public class DateFormatException extends PermitGlobalException {
+    public DateFormatException() {
+        super(ErrorCode.BAD_REQUEST_DATE_TIME_ERROR);
+    }
+}

검증 스크립트:


🏁 Script executed:

#!/bin/bash
# PermitGlobalException 생성자 확인
rg -nP 'class\s+PermitGlobalException\b' -C3
rg -nP '\bPermitGlobalException\s*\(' -n -C2

# 전역 예외 핸들러에서 DateFormatException/ErrorCode 매핑 여부 확인
rg -n 'DateFormatException|BAD_REQUEST_DATE_TIME_ERROR|INTERNAL_ISO_DATE_ERROR' -C2

Length of output: 8010


🏁 Script executed:

# PermitGlobalException 생성자 및 멤버 확인
fd PermitGlobalException.java src --exec sed -n '1,200p' {}

# 전역 예외 핸들러(@ControllerAdvice) 검색
rg -n '@ControllerAdvice|@RestControllerAdvice' -C2 src

Length of output: 6392


🏁 Script executed:

#!/bin/bash
# GlobalExceptionHandler 상단 로직 확인
sed -n '1,200p' src/main/java/com/permitseoul/permitserver/global/handler/GlobalExceptionHandler.java

Length of output: 8999


🏁 Script executed:

#!/bin/bash
# PaymentExceptionHandler 상단 로직 확인
sed -n '1,200p' src/main/java/com/permitseoul/permitserver/domain/payment/api/PaymentExceptionHandler.java

# PaymentBadRequestException 클래스 위치 및 기본 생성자 확인
rg -n 'class\s\+PaymentBadRequestException' -C3 src
sed -n '1,200p' src/main/java/com/permitseoul/permitserver/domain/payment/api/PaymentBadRequestException.java

Length of output: 1513


PermitGlobalException에 ErrorCode 생성자 추가 및 DateFormatException 전용 핸들러 구현
PermitGlobalExceptionErrorCode 전달 생성자가 없어 DateFormatExceptionsuper(ErrorCode…) 호출 시 컴파일 오류가 발생합니다. 먼저

  • src/main/java/com/permitseoul/permitserver/global/exception/PermitGlobalException.java에 ErrorCode 필드와 인자를 받는 생성자 추가
  • src/main/java/com/permitseoul/permitserver/global/handler/GlobalExceptionHandler.java에 @ExceptionHandler(DateFormatException.class)를 구현해 ApiResponseUtil.failure(e.getErrorCode())를 반환하도록 수정
    하세요.
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/global/exception/DateFormatException.java
around line 4 and related files: PermitGlobalException and
GlobalExceptionHandler need updates so DateFormatException can pass an ErrorCode
and be handled. Add an ErrorCode field, constructor that accepts ErrorCode, and
a getter in
src/main/java/com/permitseoul/permitserver/global/exception/PermitGlobalException.java;
update DateFormatException to call super(...) with the appropriate ErrorCode
constant (e.g., ErrorCode.DATE_FORMAT or whichever existing code fits); and in
src/main/java/com/permitseoul/permitserver/global/handler/GlobalExceptionHandler.java
add a method annotated with @ExceptionHandler(DateFormatException.class) that
returns ApiResponseUtil.failure(e.getErrorCode()) for the thrown exception.

INTERNAL_TICKET_ALGORITHM_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50003, "티켓 코드 생성 알고리즘 에러입니다."),
INTERNAL_PAYMENT_FEIGN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50004, "결제 feign 통신 에러입니다."),
INTERNAL_ISO_DATE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50005, "iso date string에서 localdate로 변환 과정 에러입니다."),
INTERNAL_ISO_DATE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50005, "Toss iso date string에서 localdate로 변환 과정 에러입니다."),
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

INTERNAL_ISO_DATE_ERROR 메시지 변경은 OK, 하지만 매핑 예외 타입과의 불일치 존재

  • "Toss iso date string…"으로 출처 명확화는 좋습니다. 다만 이 코드는 500(서버 내부) 취지인데, PaymentService.cancelPayment에서는 DateFormatException을 PaymentBadRequestException(ErrorCode.INTERNAL_ISO_DATE_ERROR)로 변환하고 있습니다. 예외 타입 명칭(…BadRequest…)과 500 상태 코드가 상충합니다. 서비스 단에서 500용 예외 타입(예: PaymentInternalException)으로 매핑하거나, 최소한 BAD_REQUEST_DATE_TIME_ERROR(400)와 혼용되지 않도록 정리해 주세요.

🏁 Script executed:

#!/bin/bash
# 검색: PaymentService.cancelPayment 메소드 위치 확인
rg -n "cancelPayment" -C3 --glob "*.java" src

# 검색: PaymentBadRequestException 클래스 정의 위치 확인
rg -n "class PaymentBadRequestException" -C3 --glob "*.java" src

Length of output: 6391


🏁 Script executed:

#!/bin/bash
# PaymentService.cancelPayment 메소드 내 DateFormatException 매핑 확인
sed -n '180,260p' src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java

Length of output: 4966


🏁 Script executed:

#!/bin/bash
# Locate PaymentApiException
rg -n "class PaymentApiException" -C3 --glob "*.java" src

# If found, inspect its implementation
rg -n "public abstract class PaymentApiException" -C3 --glob "*.java" src

# Search for global exception handler
rg -n "@ControllerAdvice" -C3 --glob "*.java" src

Length of output: 1485


🏁 Script executed:

#!/bin/bash
# Search for BAD_REQUEST_DATE_TIME_ERROR in ErrorCode enum
rg -n "BAD_REQUEST_DATE_TIME_ERROR" -C3 --glob "*.java" src

Length of output: 1946


🏁 Script executed:

#!/bin/bash
# Inspect ErrorCode enum around BAD_REQUEST_DATE_TIME_ERROR
rg -n "BAD_REQUEST_DATE_TIME_ERROR" -C3 src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java

Length of output: 403


INTERNAL_ISO_DATE_ERROR 매핑 예외 타입 불일치
src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java의 catch(DateFormatException) 블록에서
throw new PaymentBadRequestException(ErrorCode.INTERNAL_ISO_DATE_ERROR);으로 500(Internal Server Error) 코드를 BadRequestException으로 처리하고 있습니다.

  • 500 에러는 내부 서버용 예외(PaymentInternalException 등)로 던지거나
  • Bad Request(400)용 에러코드(BAD_REQUEST_DATE_TIME_ERROR)로 변경하여 예외 타입·HTTP 상태가 일치하도록 수정하세요.
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java
around line 110, the INTERNAL_ISO_DATE_ERROR is defined as an Internal Server
Error (500) but PaymentService catches DateFormatException and rethrows it as a
BadRequestException, causing a mismatch between error code and exception type;
fix by either (A) change the enum entry to a 400-level
BAD_REQUEST_DATE_TIME_ERROR (HttpStatus.BAD_REQUEST) so the ErrorCode aligns
with BadRequestException, or (B) keep INTERNAL_ISO_DATE_ERROR as 500 and update
the catch block in
src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java
to throw a PaymentInternalException (or equivalent internal-server exception)
with ErrorCode.INTERNAL_ISO_DATE_ERROR; make the change that preserves
consistent HTTP status and exception type across the service and error-code
enum.

Comment on lines 79 to 87
// update할 때, 결합
public static LocalDateTime combineDateAndTimeForUpdate(final LocalDate date, final LocalTime time, final LocalDateTime originalDateTime) {
if (date == null && time == null) {
return originalDateTime;
}
final LocalDate localDate = date == null ? originalDateTime.toLocalDate() : date;
final LocalTime localTime = time == null ? originalDateTime.toLocalTime() : time;
return LocalDateTime.of(localDate, localTime);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

originalDateTime이 null인 경우 NPE 가능성

date 또는 time 중 하나만 전달되고 originalDateTime이 null이면 NPE가 납니다. 부분 결합 시에만 originalDateTime이 필요하도록 가드 추가를 권장합니다.

-    public static LocalDateTime combineDateAndTimeForUpdate(final LocalDate date, final LocalTime time, final LocalDateTime originalDateTime) {
-        if (date == null && time == null) {
-            return originalDateTime;
-        }
-        final LocalDate localDate = date == null ? originalDateTime.toLocalDate() : date;
-        final LocalTime localTime = time == null ? originalDateTime.toLocalTime() : time;
-        return LocalDateTime.of(localDate, localTime);
-    }
+    public static LocalDateTime combineDateAndTimeForUpdate(final LocalDate date, final LocalTime time, final LocalDateTime originalDateTime) {
+        if (date == null && time == null) {
+            return java.util.Objects.requireNonNull(originalDateTime, "originalDateTime이 null입니다.");
+        }
+        final LocalDate localDate = (date != null) ? date : java.util.Objects.requireNonNull(originalDateTime, "originalDateTime이 null입니다.").toLocalDate();
+        final LocalTime localTime = (time != null) ? time : java.util.Objects.requireNonNull(originalDateTime, "originalDateTime이 null입니다.").toLocalTime();
+        return LocalDateTime.of(localDate, localTime);
+    }
📝 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
// update할 때, 결합
public static LocalDateTime combineDateAndTimeForUpdate(final LocalDate date, final LocalTime time, final LocalDateTime originalDateTime) {
if (date == null && time == null) {
return originalDateTime;
}
final LocalDate localDate = date == null ? originalDateTime.toLocalDate() : date;
final LocalTime localTime = time == null ? originalDateTime.toLocalTime() : time;
return LocalDateTime.of(localDate, localTime);
}
// update할 때, 결합
public static LocalDateTime combineDateAndTimeForUpdate(final LocalDate date, final LocalTime time, final LocalDateTime originalDateTime) {
if (date == null && time == null) {
return java.util.Objects.requireNonNull(originalDateTime, "originalDateTime이 null입니다.");
}
final LocalDate localDate = (date != null)
? date
: java.util.Objects.requireNonNull(originalDateTime, "originalDateTime이 null입니다.").toLocalDate();
final LocalTime localTime = (time != null)
? time
: java.util.Objects.requireNonNull(originalDateTime, "originalDateTime이 null입니다.").toLocalTime();
return LocalDateTime.of(localDate, localTime);
}
🤖 Prompt for AI Agents
In src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java
around lines 79 to 87, the method combineDateAndTimeForUpdate can NPE when
originalDateTime is null but only one of date/time is provided; add a guard so
originalDateTime is only required when date or time is null: if both date and
time are null return originalDateTime as before; if originalDateTime is null and
(date == null || time == null) throw an explicit IllegalArgumentException (or
return a sensible default per project conventions); otherwise compute localDate
= (date != null ? date : originalDateTime.toLocalDate()) and localTime = (time
!= null ? time : originalDateTime.toLocalTime()) and return
LocalDateTime.of(localDate, localTime).

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: 1

🧹 Nitpick comments (5)
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (5)

132-151: 부분 업데이트 병합 로직과 예외 매핑은 적절 — 도메인 제약 검증 위치만 확인

Null 허용 필드 병합(combineDateAndTimeForUpdate)과 NotFound/BadRequest 매핑은 깔끔합니다. 다만 아래를 확인해 주세요:

  • AdminEventUpdater 혹은 도메인 계층에서 가시기간/이벤트기간의 상호 제약(예: 시작≤종료, 필요 시 노출기간과 이벤트기간 관계)이 일관되게 검증되는지.
  • combineDateAndTimeForUpdate가 (date만/ time만) 부분 입력 시 원래 값과 기대대로 병합되는지.

필요 시 invalid 입력에 대한 최소 로깅을 추가하는 것도 디버깅에 유용합니다.


153-155: 경미: 단순 위임 래퍼 제거 고려

해당 메서드는 유틸 호출만 위임합니다. 테스트 대체/캡슐화 목적이 아니라면 인라인해도 됩니다.

-    private LocalDateTime combineDateAndTimeForUpdate(final LocalDate date, final LocalTime time, final LocalDateTime originalDateTime) {
-        return DateFormatterUtil.combineDateAndTimeForUpdate(date, time, originalDateTime);
-    }
+    // 인라인 사용: DateFormatterUtil.combineDateAndTimeForUpdate(...)

170-179: 티켓 타입 시간 필드 유효성 재확인

LocalDateTime.of(...)는 date/time 중 하나라도 null이면 NPE가 발생합니다. DTO 레벨에서 @NotNull 등으로 보장되는지, 아니면 서비스에서 검증/오류 매핑을 추가할지 결정해 주세요. (가격/수량의 음수 방지도 함께 확인 권장)


291-291: Null 안전 정렬로 보강 (옵션)

startAt가 null인 레코드가 존재할 수 있다면 Null-safe 내림차순 정렬로 바꾸는 것을 권장합니다.

-        sorted.sort(Comparator.comparing(Event::getStartAt).reversed());
+        sorted.sort(Comparator.comparing(
+                Event::getStartAt,
+                Comparator.nullsLast(Comparator.reverseOrder())
+        ));

301-304: 그룹핑 시 null 시작시간 처리 (옵션)

null startAt가 있을 경우 포맷터에서 NPE가 납니다. 스킵 또는 별도 섹션으로 분리해 주세요.

-            final LocalDateTime start = e.getStartAt();
+            final LocalDateTime start = e.getStartAt();
+            if (start == null) {
+                continue;
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1eec8f and 1e15072.

📒 Files selected for processing (2)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (8 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/exception/EventIllegalArgumentException.java (1)
  • EventIllegalArgumentException (3-4)
🔇 Additional comments (4)
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (4)

4-21: 업데이트 경로에 필요한 의존성 추가 적절

업데이트 DTO/업데이터/예외 매핑을 위한 import 구성은 타당합니다.


52-52: AdminEventUpdater 의존성 주입 OK

서비스에서 업데이트 로직을 위임하기 위한 필드 추가가 적절합니다.


87-97: 상세 응답의 날짜/시간 포맷 계약 재확인 필요

"yyyyMMdd", "HHmm" 포맷으로 변경/일원화된 점 좋습니다. 다만 FE와의 계약(예: 선행 0 보장, 로컬타임/타임존 처리)이 이 포맷으로 합의되었는지 확인 부탁드립니다.


216-216: 생성 경로의 날짜/시간 결합 사용 적절

생성 플로우에서 combineDateAndTime 사용은 일관성에 도움이 됩니다.

Comment on lines 158 to 166
List<AdminEventWithTicketCreateRequest.AdminEventImageInfo> eventImages) {
final List<EventImageEntity> eventImageEntityList = IntStream.range(0, eventImages.size())
.mapToObj(i -> EventImageEntity.create(
eventId,
eventImages.get(i).imageUrl().trim(),
i+1
)).toList();
.mapToObj(i -> EventImageEntity.create(
eventId,
eventImages.get(i).imageUrl().trim(),
i+1
))
.toList();
adminEventImageSaver.saveEventImages(eventImageEntityList);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

NPE/불량 데이터 방지: 이미지 리스트/URL 검증 및 시퀀스 안정화

eventImages 또는 imageUrl이 null/blank이면 NPE나 불량 데이터 저장 위험이 있습니다. null/blank를 걸러내고, 시퀀스는 연속적으로 재부여하는 편이 안전합니다.

-        final List<EventImageEntity> eventImageEntityList = IntStream.range(0, eventImages.size())
-                .mapToObj(i -> EventImageEntity.create(
-                        eventId,
-                        eventImages.get(i).imageUrl().trim(),
-                        i+1
-                ))
-                .toList();
+        final AtomicInteger seq = new AtomicInteger(1);
+        final List<EventImageEntity> eventImageEntityList =
+                (eventImages == null ? List.<AdminEventWithTicketCreateRequest.AdminEventImageInfo>of() : eventImages).stream()
+                        .filter(Objects::nonNull)
+                        .map(AdminEventWithTicketCreateRequest.AdminEventImageInfo::imageUrl)
+                        .filter(url -> url != null && !url.isBlank())
+                        .map(url -> EventImageEntity.create(eventId, url.trim(), seq.getAndIncrement()))
+                        .toList();

추가 import:

import java.util.concurrent.atomic.AtomicInteger;
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java
around lines 158 to 166, eventImages and individual imageUrl values are not
validated which can cause NPEs or store bad data; filter out null eventImages
and blank/empty imageUrl values, trim URLs, and rebuild a new list of
EventImageEntity with a stable, consecutive sequence (use an AtomicInteger or a
simple counter to assign sequence numbers starting at 1) before calling
adminEventImageSaver.saveEventImages; also add the import for
java.util.concurrent.atomic.AtomicInteger if you choose that approach.

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: 4

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (2)

76-79: 잔여수량 음수 가능성 차단

decreaseTicketCount가 재고 체크 없이 감소시키므로 음수 잔여가 될 수 있습니다. 내부 사용 실수에도 안전하도록 방어 로직을 포함해주세요.

     public void decreaseTicketCount(final int buyTicketCount) {
         checkBuyTicketCountZero(buyTicketCount);
-        this.remainTicketCount -= buyTicketCount;
+        if (this.remainTicketCount < buyTicketCount) {
+            throw new TicketTypeInsufficientCountException();
+        }
+        this.remainTicketCount -= buyTicketCount;
     }

81-84: 총수량 초과 증가 방지

취소/환불 복원 등으로 증가 시 총수량을 초과하지 않아야 합니다. 현재는 상한 검증이 없어 데이터 무결성이 깨질 수 있습니다.

     public void increaseTicketCount(final int buyTicketCount) {
         checkBuyTicketCountZero(buyTicketCount);
-        this.remainTicketCount += buyTicketCount;
+        if (this.remainTicketCount + buyTicketCount > this.totalTicketCount) {
+            throw new TicketTypeIllegalException("남은 수량이 총 수량을 초과할 수 없습니다.");
+        }
+        this.remainTicketCount += buyTicketCount;
     }
♻️ Duplicate comments (3)
src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/entity/TicketRoundEntity.java (1)

33-40: 생성 시점 유효성 검증 추가: 반영 잘 됐습니다

생성자에서 판매기간 검증을 호출하도록 한 점 좋습니다. 도메인 일관성 확보에 도움이 됩니다.

src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (1)

37-41: DB 마이그레이션(컬럼 rename) 누락/정합성 재확인 필요

엔티티 컬럼명이 ticket_start_at, ticket_end_at로 변경되었습니다. 해당 테이블에 대해 RENAME COLUMN 마이그레이션과 인덱스/제약/뷰/네이티브 쿼리 참조 업데이트가 모두 반영되었는지 확인 부탁드립니다.

#!/bin/bash
# 1) 코드/리소스에 남아있는 구 컬럼명 참조 탐색
rg -nP -C2 '\b(ticket_start_date|ticket_end_date)\b' -g 'src/**' --glob '!**/build/**' --glob '!**/target/**' || true

# 2) 마이그레이션 스크립트 내 rename/alter 포함 여부 탐색
rg -nP -C2 '(alter\s+table|rename\s+column).*ticket_type' src/main/resources || true
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (1)

53-66: 유효성 검증 호출 시점은 적절하나, 현재 구현이 잘못되어 결과가 역전됩니다

생성자 초기에 validateDateTime을 호출하는 위치는 좋습니다. 다만 실제 구현(아래 92-96라인)이 ‘정상 케이스’에서 예외를 던지도록 반대로 되어 있습니다. 또한 null 방어가 없어 NPE 위험이 있습니다.

🧹 Nitpick comments (10)
src/main/java/com/permitseoul/permitserver/domain/eventimage/core/repository/EventImageRepository.java (1)

19-21: 벌크 삭제는 트랜잭션 필요 + 삭제 건수 반환으로 가시성 확보 제안

  • @Modifying JPQL delete는 활성 트랜잭션이 없으면 실패합니다. 상위 계층(@service 등)에서 @transactional로 감싸져 있는지 확인해 주세요. 안전장치로 이 메서드를 호출하는 서비스/컴포넌트에 @transactional을 명시하는 것을 권장합니다.
  • 운영 가시성을 위해 삭제된 행 수를 반환(int)하도록 하면 로깅/검증이 수월합니다.
  • flushAutomatically는 대부분 불필요합니다(대부분의 서비스 계층에서 트랜잭션 커밋 시 flush). 유지가 꼭 필요하지 않다면 제거를 권장합니다.

제안 diff:

-    @Modifying(clearAutomatically = true, flushAutomatically = true)
-    @Query("delete from EventImageEntity e where e.eventId = :eventId")
-    void deleteAllByEventId(@Param("eventId") final long eventId);
+    @Modifying(clearAutomatically = true)
+    @Query("DELETE FROM EventImageEntity e WHERE e.eventId = :eventId")
+    int deleteAllByEventId(@Param("eventId") final long eventId);
src/main/java/com/permitseoul/permitserver/domain/ticketround/core/exception/TicketRoundIllegalArgumentException.java (1)

3-4: 메시지/원인 전달용 생성자 추가 권장

스택트레이스만으로 원인 파악이 어렵습니다. 코어 예외가 (String), (String, Throwable) 생성자를 지원한다는 전제하에 아래처럼 생성자를 추가해 주세요.

-public class TicketRoundIllegalArgumentException extends TicketRoundCoreException {
-}
+public class TicketRoundIllegalArgumentException extends TicketRoundCoreException {
+    public TicketRoundIllegalArgumentException() {
+        super("잘못된 티켓 라운드 인자입니다.");
+    }
+    public TicketRoundIllegalArgumentException(String message) {
+        super(message);
+    }
+    public TicketRoundIllegalArgumentException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/entity/TicketRoundEntity.java (3)

27-31: 컬럼명 변경(DB 마이그레이션) 적용 여부 확인

sales_start_at / sales_end_at로 매핑이 변경되었습니다. 운영 DB에 컬럼 리네임/인덱스 영향(FK/검색 인덱스)까지 포함된 마이그레이션이 본 PR 또는 별도 PR로 포함되어 있는지 확인 부탁드립니다. Flyway/Liquibase 스크립트 누락 시 런타임 매핑 에러가 발생합니다.


33-35: 메서드명 오탈자 수정 + NPE 방지 + 예외 메시지 포함 제안

  • validateDateTIme → validateDateTime (오탈자)
  • null 전달 시 NPE 대신 도메인 예외로 전환
  • 예외에 문제 구간(start/end)을 포함해 디버깅 용이성 향상
-        validateDateTIme(salesStartAt, salesEndAt);
+        validateDateTime(salesStartAt, salesEndAt);
-    private void validateDateTIme(final LocalDateTime salesStartAt, final LocalDateTime salesEndAt) {
-        if (salesStartAt.isAfter(salesEndAt)) {
-            throw new TicketRoundIllegalArgumentException();
-        }
-    }
+    private void validateDateTime(final LocalDateTime salesStartAt, final LocalDateTime salesEndAt) {
+        if (salesStartAt == null || salesEndAt == null) {
+            throw new TicketRoundIllegalArgumentException("salesStartAt/endAt must not be null");
+        }
+        if (salesStartAt.isAfter(salesEndAt)) {
+            throw new TicketRoundIllegalArgumentException(
+                String.format("Invalid sales period: startAt(%s) > endAt(%s)", salesStartAt, salesEndAt)
+            );
+        }
+    }

Also applies to: 55-59


49-53: 경계 포함 여부 및 예외 도메인 의미 확인

현재 now == startAt/endAt는 허용(포함)입니다. 요구사항이 [시작/종료 시각 포함]이 맞는지 확인 부탁드립니다. 또한 시작 전에도 Expired 예외를 던지는 이름은 의미가 애매할 수 있어, 요구사항에 따라 NotStarted/Unavailable 등으로 구분 또는 명칭 조정 고려 바랍니다.

src/main/java/com/permitseoul/permitserver/domain/tickettype/core/exception/TicketTypeIllegalException.java (1)

3-4: 예외에 표준 생성자/기본 메시지 추가 제안

도메인 예외에 의미 있는 기본 메시지와 오버로드된 생성자를 제공하면 핸들러/로그에서 가독성이 좋아집니다.

 public class TicketTypeIllegalException extends TicketTypeCoreException {
-}
+    private static final String DEFAULT_MESSAGE = "유효하지 않은 티켓 타입 시간 범위입니다.";
+
+    public TicketTypeIllegalException() {
+        super(DEFAULT_MESSAGE);
+    }
+
+    public TicketTypeIllegalException(final String message) {
+        super(message);
+    }
+
+    public TicketTypeIllegalException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (1)

43-58: 초기 값 유효성(총수량/가격/이름) 검증 추가 제안

생성 시 음수/영 값이 들어오면 무결성 문제가 발생합니다. 최소한 총수량>0, 가격 >= 0, 이름 공백 불가를 체크하면 좋습니다.

     private TicketTypeEntity(long ticketRoundId,
                             String ticketTypeName,
                             BigDecimal ticketPrice,
                             int totalTicketCount,
                             LocalDateTime ticketStartAt,
                             LocalDateTime ticketEndAt) {
         validateDateTime(ticketStartAt, ticketEndAt);
+        if (totalTicketCount <= 0) {
+            throw new TicketTypeIllegalException("총 수량은 1 이상이어야 합니다.");
+        }
+        if (ticketTypeName == null || ticketTypeName.isBlank()) {
+            throw new TicketTypeIllegalException("티켓 타입명은 공백일 수 없습니다.");
+        }
+        if (ticketPrice == null || ticketPrice.compareTo(BigDecimal.ZERO) < 0) {
+            throw new TicketTypeIllegalException("티켓 가격은 음수일 수 없습니다.");
+        }
 
         this.ticketRoundId = ticketRoundId;
         this.ticketTypeName = ticketTypeName;
         this.ticketPrice = ticketPrice;
         this.totalTicketCount = totalTicketCount;
         this.remainTicketCount = totalTicketCount;
         this.ticketStartAt = ticketStartAt;
         this.ticketEndAt = ticketEndAt;
     }

또한 스키마 측면에서도 ticket_type_namenullable = falseticket_priceprecision/scale 지정 고려 바랍니다.

-    @Column(name = "ticket_type_name")
+    @Column(name = "ticket_type_name", nullable = false)
     private String ticketTypeName;
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/exception/TimeTableIllegalArgumentException.java (1)

3-3: 클래스명 컨벤션 정합성(“TimeTable” → “Timetable”)

프로젝트 전반에서 Timetable*로 표기하는 것으로 보입니다. 본 예외만 TimeTable*로 혼용되어 검색성과 일관성이 떨어집니다. 가능하면 TimetableIllegalArgumentException으로 리네임하고 참조 import를 정리해 주세요.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (2)

3-3: 예외 클래스 네이밍 불일치

TimeTableIllegalArgumentException import는 네이밍 컨벤션(Timetable*)과 상충됩니다. 예외명을 리네임할 경우 본 import도 함께 정리되어야 합니다.


79-90: 정적 팩토리 사용은 적절합니다

생성자 위임 및 단일 경로 유효성 검증 유지가 좋아 보입니다. 추후 파라미터 수가 더 늘면 빌더 도입을 고려해도 되겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e15072 and c9307bb.

📒 Files selected for processing (12)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java (9 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/eventimage/core/component/AdminEventImageRemover.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventimage/core/repository/EventImageRepository.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (4 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/exception/TimeTableIllegalArgumentException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/entity/TicketRoundEntity.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticketround/core/exception/TicketRoundIllegalArgumentException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/tickettype/core/exception/TicketTypeIllegalException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/dto/req/AdminEventUpdateRequest.java
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/core/component/AdminEventUpdater.java
  • src/main/java/com/permitseoul/permitserver/domain/admin/event/api/service/AdminEventService.java
  • src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (1)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/exception/TimeTableIllegalArgumentException.java (1)
  • TimeTableIllegalArgumentException (3-5)
src/main/java/com/permitseoul/permitserver/domain/ticketround/core/domain/entity/TicketRoundEntity.java (1)
src/main/java/com/permitseoul/permitserver/domain/ticketround/core/exception/TicketRoundIllegalArgumentException.java (1)
  • TicketRoundIllegalArgumentException (3-4)
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (1)
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/exception/TicketTypeIllegalException.java (1)
  • TicketTypeIllegalException (3-4)
🔇 Additional comments (3)
src/main/java/com/permitseoul/permitserver/domain/eventimage/core/repository/EventImageRepository.java (1)

5-5: @Modifying import 추가 적절

벌크 삭제 쿼리 사용을 위해 필요한 import가 정확히 추가되었습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/eventimage/core/component/AdminEventImageRemover.java (1)

3-14: 트랜잭션 경계 및 반환 타입 검토

  • AdminEventImageRemover.deleteAllByEventId는 현재 AdminEventService의 @transactional 메서드 내부에서만 호출되므로, 컴포넌트에 별도 @transactional을 부여하면 중복일 수 있습니다.
  • EventImageRepository.deleteAllByEventId는 void를 반환 중이므로, 삭제 건수를 활용하려면 리포지토리 인터페이스를 먼저 int 반환으로 변경하고, JPQL 벌크 삭제 쿼리를 이에 맞춰 수정해야 합니다.
  • S3 등 외부 스토리지에서 실제 이미지 삭제가 필요하다면, DB 삭제 전 키 수집 → 스토리지 삭제 → DB 벌크 삭제 순으로 처리하고, 캐시 무효화도 함께 고려하세요.
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java (1)

3-3: 새 도메인 예외 도입 사용 👍

TicketTypeIllegalException 도입 및 사용 방향성 좋습니다.

Comment on lines +31 to +35
@Column(name = "start_at", nullable = false)
private LocalDateTime startAt;

@Column(name = "end_date", nullable = false)
private LocalDateTime endDate;
@Column(name = "end_at", nullable = false)
private LocalDateTime endAt;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

컬럼명 변경(start_date/end_date → start_at/end_at)에 대한 마이그레이션 확인 필요

엔티티 컬럼이 변경되었습니다. 운영 DB 스키마를 위한 마이그레이션(예: Flyway/Liquibase) 추가 여부를 확인해 주세요. 기존 데이터 보존을 위한 rename/backup 전략도 필요합니다.

다음 스크립트로 관련 마이그레이션/잔존 레거시 참조를 빠르게 스캔할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# 1) 컬럼 변경 관련 마이그레이션 존재 여부
rg -n -i -g '**/*.sql' 'event_timetable_block|start_at|end_at|start_date|end_date' -C2

# 2) 자바 코드에 남은 startDate/endDate 참조 확인
rg -n -i --type=java '\b(startDate|endDate)\b' -C2

Length of output: 18833


🏁 Script executed:

#!/bin/bash
# 1) 리소스 폴더 내 SQL/YAML/XML 마이그레이션 파일 검색
find src/main/resources -type f \( -name "*.sql" -o -name "*.xml" -o -name "*.yaml" -o -name "*.yml" \)

# 2) db/migration 디렉터리 패턴 확인
find src -type f | grep -i 'db.*migration'

# 3) Flyway/Liquibase 참조 검색
grep -Rni 'flyway' -n src || true
grep -Rni 'liquibase' -n src || true

Length of output: 179


Flyway/Liquibase 마이그레이션 파일 추가 필요
레포지토리에 SQL/XML/YAML 마이그레이션 파일이나 Flyway/Liquibase 설정이 전혀 없습니다. start_datestart_at, end_dateend_at 컬럼명 변경을 반영하는 스키마 마이그레이션을 반드시 추가하고, 기존 데이터 보존을 위한 RENAME 또는 백업 전략을 구현하세요.

🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java
lines 31-35: the entity renamed columns from start_date/end_date to
start_at/end_at but no DB migration exists; add a Flyway
(src/main/resources/db/migration/V{next}__rename_start_end_columns.sql) or
Liquibase changelog that safely renames start_date→start_at and end_date→end_at
while preserving data (use ALTER TABLE ... RENAME COLUMN if DB supports it,
otherwise add new columns, copy data, verify, then drop old columns), include a
proper rollback/undo step and update project migration config so CI runs the
migration. Ensure the migration is idempotent, tested against existing data, and
referenced in the repository’s migration folder.

Comment on lines 92 to 96
private void validateDateTime(final LocalDateTime startAt, final LocalDateTime endAt) {
if (startAt.isBefore(endAt)) {
throw new TimeTableIllegalArgumentException();
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

시간 구간 검증 로직 반전 및 null 방어 추가

현재는 startAt.isBefore(endAt)일 때(정상) 예외를 던집니다. 의도대로면 startAt.isAfter(endAt)에서만 예외여야 하며, null 체크도 필요합니다. 기본 메시지도 포함해 주세요.

-    private void validateDateTime(final LocalDateTime startAt, final LocalDateTime endAt) {
-        if (startAt.isBefore(endAt)) {
-            throw new TimeTableIllegalArgumentException();
-        }
-    }
+    private void validateDateTime(final LocalDateTime startAt, final LocalDateTime endAt) {
+        if (startAt == null || endAt == null) {
+            throw new TimeTableIllegalArgumentException("startAt/endAt must not be null");
+        }
+        // startAt > endAt 인 경우만 예외
+        if (startAt.isAfter(endAt)) {
+            throw new TimeTableIllegalArgumentException("startAt must be <= endAt");
+        }
+    }

또한 “동일 시각(startAt == endAt)” 허용 여부가 기획 요구사항과 일치하는지도 확인 부탁드립니다. 불허라면 !startAt.isBefore(endAt) 조건으로 변경하십시오.


🏁 Script executed:

#!/bin/bash
# validateDateTime 메서드 구현 위치 및 로직 확인
rg -nC3 'validateDateTime' --type java

Length of output: 2266


🏁 Script executed:

#!/bin/bash
# 예외 클래스 정의 위치 확인
grep -R "class TimeTableIllegalArgumentException" -n src/main/java
# 필드 선언 확인
grep -R "private .*LocalDateTime startAt" -n src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java
grep -R "private .*LocalDateTime endAt" -n src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java

Length of output: 464


🏁 Script executed:

#!/bin/bash
# 테스트 파일 존재 여부 확인
find src/test/java -type f

# 테스트에서 validateDateTime 사용 여부 확인
grep -R "validateDateTime" -n src/test/java

# 테스트에서 TimeTableIllegalArgumentException 사용 여부 확인
grep -R "TimeTableIllegalArgumentException" -n src/test/java

Length of output: 330


🏁 Script executed:

#!/bin/bash
# TimeTableIllegalArgumentException 클래스의 생성자 확인
sed -n '1,200p' src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/exception/TimeTableIllegalArgumentException.java

Length of output: 158


validateDateTime 조건 반전·null 검사·예외 메시지 생성자 추가 필요

  • validateDateTime 내부 로직이 반대로 구현되어 있어, startAt.isAfter(endAt)인 경우에만 예외를 던지도록 조건을 반전해야 합니다.
  • startAt 또는 endAt이 null일 때 NPE가 발생하므로 명시적 null 검사 및 예외 처리 추가를 검토하세요.
  • 예외 메시지를 사용하려면 현재 기본 생성자만 있는 TimeTableIllegalArgumentException(String message) 생성자를 정의해 super(message)를 호출해야 합니다.
  • startAt == endAt 허용 여부는 기획 요구사항을 확인 후, 불허 시 !startAt.isBefore(endAt) 조건으로 변경하세요.
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java
around lines 92-96, the validateDateTime logic is inverted and lacks null checks
and informative exception messaging; change the condition to throw when startAt
is after endAt (i.e., startAt.isAfter(endAt)), add explicit null checks for
startAt and endAt and throw TimeTableIllegalArgumentException with a clear
message when either is null, and update or add a constructor in
TimeTableIllegalArgumentException that accepts a String and calls
super(message); additionally confirm whether startAt == endAt should be
allowed—if not, use a not-before check (e.g., !startAt.isBefore(endAt)) to
trigger the exception.

Comment on lines +3 to +5
public class TimeTableIllegalArgumentException extends RuntimeException {

}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

도메인 예외는 IllegalArgumentException을 상속하고 유의미한 메시지를 제공하세요

엔티티 유효성 검증에서 발생하는 오류이므로 RuntimeException보단 IllegalArgumentException 상속이 의도에 더 적합합니다. 또한 현재는 메시지/원인 전달이 불가해 추적이 어렵습니다. 아래처럼 생성자를 추가해 주세요.

-public class TimeTableIllegalArgumentException extends RuntimeException {
-
-}
+public class TimeTableIllegalArgumentException extends IllegalArgumentException {
+    public TimeTableIllegalArgumentException() { super(); }
+    public TimeTableIllegalArgumentException(String message) { super(message); }
+    public TimeTableIllegalArgumentException(String message, Throwable cause) { super(message, cause); }
+    public TimeTableIllegalArgumentException(Throwable cause) { super(cause); }
+}
📝 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
public class TimeTableIllegalArgumentException extends RuntimeException {
}
public class TimeTableIllegalArgumentException extends IllegalArgumentException {
public TimeTableIllegalArgumentException() { super(); }
public TimeTableIllegalArgumentException(String message) { super(message); }
public TimeTableIllegalArgumentException(String message, Throwable cause) { super(message, cause); }
public TimeTableIllegalArgumentException(Throwable cause) { super(cause); }
}
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/exception/TimeTableIllegalArgumentException.java
around lines 3-5, the domain exception currently extends RuntimeException and
has no constructors or message propagation; change it to extend
IllegalArgumentException and add standard constructors that accept a message, a
message plus cause, and a cause (so callers can pass meaningful messages and
underlying causes), keeping the class public and otherwise minimal.

Comment on lines +47 to +50
LocalDateTime ticketStartAt,
LocalDateTime ticketEndAt) {
validateDatTime(ticketStartAt, ticketEndAt);

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

시간 검증 메서드 타이포 수정 + NPE/동치 허용 여부 명확화

  • 메서드명 validateDatTime 오타입니다.
  • isAfter만 체크하면 시작=종료가 허용됩니다. 도메인 규칙이 “엄격히 이전(<?)”라면 동치도 금지해야 합니다.
  • null 전달 시 NPE가 나므로 명시적으로 검증하고 도메인 예외를 던지는 편이 안전합니다.
-        validateDatTime(ticketStartAt, ticketEndAt);
+        validateDateTime(ticketStartAt, ticketEndAt);
@@
-        this.ticketStartAt = ticketStartAt;
-        this.ticketEndAt = ticketEndAt;
+        this.ticketStartAt = ticketStartAt;
+        this.ticketEndAt = ticketEndAt;
@@
-    private void validateDatTime(final LocalDateTime ticketStartAt, final LocalDateTime ticketEndAt) {
-        if (ticketStartAt.isAfter(ticketEndAt)) {
-            throw new TicketTypeIllegalException();
-        }
-    }
+    private void validateDateTime(final LocalDateTime ticketStartAt, final LocalDateTime ticketEndAt) {
+        if (ticketStartAt == null || ticketEndAt == null) {
+            throw new TicketTypeIllegalException("ticketStartAt/ticketEndAt은 null일 수 없습니다.");
+        }
+        // 규칙: 시작은 종료보다 '엄격히 이전'이어야 함(동치 금지). 동치를 허용하려면 isBefore -> isAfter 체크로 되돌려주세요.
+        if (!ticketStartAt.isBefore(ticketEndAt)) {
+            throw new TicketTypeIllegalException("ticketStartAt은 ticketEndAt보다 이전이어야 합니다.");
+        }
+    }

도메인에서 시작=종료를 허용해야 한다면 위 조건을 if (ticketStartAt.isAfter(ticketEndAt))로 조정 바랍니다.

Also applies to: 56-57, 92-96


메서드명 오타 수정 및 날짜 검증 강화

  • validateDatTimevalidateDateTime로 변경
  • ticketStartAt/ticketEndAt가 null일 경우 명시적으로 예외 처리 (NPE 방지)
  • 도메인 규칙이 “시작 < 종료”라면 동치(=)도 금지하도록 isBefore 사용, 동치 허용 시 isAfter로 복원
--- a/src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java
@@ -47,9 +47,17 @@ public class TicketTypeEntity {
-        validateDatTime(ticketStartAt, ticketEndAt);
+        validateDateTime(ticketStartAt, ticketEndAt);

-    private void validateDatTime(final LocalDateTime ticketStartAt, final LocalDateTime ticketEndAt) {
-        if (ticketStartAt.isAfter(ticketEndAt)) {
-            throw new TicketTypeIllegalException();
-        }
-    }
+    private void validateDateTime(final LocalDateTime ticketStartAt, final LocalDateTime ticketEndAt) {
+        if (ticketStartAt == null || ticketEndAt == null) {
+            throw new TicketTypeIllegalException("ticketStartAt/ticketEndAt은 null일 수 없습니다.");
+        }
+        // 시작은 종료보다 엄격히 이전이어야 함(동치 금지)
+        if (!ticketStartAt.isBefore(ticketEndAt)) {
+            throw new TicketTypeIllegalException("ticketStartAt은 ticketEndAt보다 이전이어야 합니다.");
+        }
+    }
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/tickettype/core/domain/entity/TicketTypeEntity.java
around lines 47 to 50, rename the misspelled method call validateDatTime to
validateDateTime, and update the validation to explicitly check for nulls on
ticketStartAt and ticketEndAt (throw a clear IllegalArgumentException or
DomainValidationException to avoid NPE), then enforce the domain rule using
ticketStartAt.isBefore(ticketEndAt) so equality is rejected (use isAfter only if
equality should be allowed); make sure to update the method signature/name and
all internal calls accordingly.

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/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)

57-81: create(): 날짜 유효성 검증이 누락되어 있습니다.

엔터티 생성 시에도 이벤트 기간/가시성 기간을 검증해야 불량 데이터 유입을 막을 수 있습니다.

     public static EventEntity create(final String name,
                                      final EventType eventType,
                                      final LocalDateTime startDate,
                                      final LocalDateTime endDate,
                                      final String venue,
                                      final String lineUp,
                                      final String details,
                                      final int minAge,
                                      final LocalDateTime visibleStartAt,
                                      final LocalDateTime visibleEndAt,
                                      final String ticketCheckCode) {
+        validateDate(startDate, endDate);
+        validateDate(visibleStartAt, visibleEndAt);
         return EventEntity.builder()
                 .name(name)
                 .eventType(eventType)
                 .startAt(startDate)
                 .endAt(endDate)
                 .venue(venue)
                 .lineUp(lineUp)
                 .details(details)
                 .minAge(minAge)
                 .visibleStartAt(visibleStartAt)
                 .visibleEndAt(visibleEndAt)
                 .ticketCheckCode(ticketCheckCode)
                 .build();
     }
🧹 Nitpick comments (3)
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (3)

30-35: 조회/정렬 패턴 대비 인덱스 및 제약 검토를 권장합니다.

이벤트 노출/기간 기반 조회가 빈번하다면 다음 인덱스를 고려해 주세요. 티켓 검증 코드의 유일성 여부도 확인 바랍니다.

  • 권장 인덱스: events(start_at), events(visible_start_at), events(visible_end_at)
  • 선택: events(event_type, start_at) 복합 인덱스
  • ticket_check_code가 유니크해야 한다면 UNIQUE 제약 추가

예시 DDL:

  • CREATE INDEX idx_events_start_at ON events(start_at);
  • CREATE INDEX idx_events_visible_start_at ON events(visible_start_at);
  • CREATE INDEX idx_events_visible_end_at ON events(visible_end_at);
  • ALTER TABLE events ADD CONSTRAINT uq_events_ticket_check_code UNIQUE (ticket_check_code);

Also applies to: 48-52, 54-55


112-114: 예외 정보가 비어 있어 디버깅이 어렵습니다.

원인 식별 메시지/코드(예: INVALID_EVENT_PERIOD, INVALID_VISIBLE_PERIOD, NEGATIVE_MIN_AGE)를 포함해 주세요.

원하시면 EventIllegalArgumentException에 생성자/정적 팩토리 추가와 이 파일의 throw 지점 교체 패치까지 만들어 드립니다.


65-67: EventEntity.create 파라미터명(startAt/endAt) 변경 시 호출부 수정 필요

  • AdminEventSaver.java:29 외 모든 EventEntity.create 호출부에서 startDate → startAt, endDate → endAt로 반영하십시오.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9307bb and 481028c.

📒 Files selected for processing (2)
  • src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (4 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/exception/EventIllegalArgumentException.java (1)
  • EventIllegalArgumentException (3-4)

Comment on lines 45 to 46
@Column(name = "min_age")
private int minAge;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

min_age 컬럼 null 허용과 기본형 int 매핑이 어긋납니다.

DB는 null을 허용하고, 자바 필드는 primitive라 null을 표현할 수 없습니다. 스키마를 맞춰주세요.

-    @Column(name = "min_age")
+    @Column(name = "min_age", nullable = false)
     private int minAge;

마이그레이션에 ALTER TABLE events MODIFY COLUMN min_age int 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
@Column(name = "min_age")
private int minAge;
@Column(name = "min_age", nullable = false)
private int minAge;
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java
around lines 45-46, the JPA mapping uses a primitive int for minAge while the DB
column min_age allows NULL; change the Java field to the wrapper type Integer
(or annotate with @Column(nullable = false) and ensure migration) so the Java
type can represent nulls, update any getters/setters/usage to handle Integer,
and add the suggested migration ALTER TABLE events MODIFY COLUMN min_age int NOT
NULL; if you decide to enforce NOT NULL instead of using Integer.

Comment on lines +83 to +108
public void updateEvent(final String name,
final EventType eventType,
final LocalDateTime startAt,
final LocalDateTime endAt,
final String venue,
final String lineUp,
final String details,
final int minAge,
final LocalDateTime visibleStartAt,
final LocalDateTime visibleEndAt,
final String ticketCheckCode) {
validateDate(startAt, endAt);
validateDate(visibleStartAt, visibleEndAt);

this.name = name;
this.eventType = eventType;
this.startAt = startAt;
this.endAt = endAt;
this.venue = venue;
this.lineUp = lineUp;
this.details = details;
this.minAge = minAge;
this.visibleStartAt = visibleStartAt;
this.visibleEndAt = visibleEndAt;
this.ticketCheckCode = ticketCheckCode;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

updateEvent(): 필수 필드 null 방지 및 minAge 음수 허용을 막아주세요.

엔터티 레벨 invariant로 보강해 두는 편이 안전합니다.

     public void updateEvent(final String name,
                             final EventType eventType,
                             final LocalDateTime startAt,
                             final LocalDateTime endAt,
                             final String venue,
                             final String lineUp,
                             final String details,
                             final int minAge,
                             final LocalDateTime visibleStartAt,
                             final LocalDateTime visibleEndAt,
                             final String ticketCheckCode) {
+        Objects.requireNonNull(name, "name");
+        Objects.requireNonNull(eventType, "eventType");
+        Objects.requireNonNull(startAt, "startAt");
+        Objects.requireNonNull(endAt, "endAt");
+        Objects.requireNonNull(venue, "venue");
+        Objects.requireNonNull(visibleStartAt, "visibleStartAt");
+        Objects.requireNonNull(visibleEndAt, "visibleEndAt");
+        Objects.requireNonNull(ticketCheckCode, "ticketCheckCode");
+        if (minAge < 0) {
+            throw new EventIllegalArgumentException();
+        }
         validateDate(startAt, endAt);
         validateDate(visibleStartAt, visibleEndAt);
 
         this.name = name;
         this.eventType = eventType;
         this.startAt = startAt;
         this.endAt = endAt;
         this.venue = venue;
         this.lineUp = lineUp;
         this.details = details;
         this.minAge = minAge;
         this.visibleStartAt = visibleStartAt;
         this.visibleEndAt = visibleEndAt;
         this.ticketCheckCode = ticketCheckCode;
     }

파일 상단에 다음 import도 추가하세요:

+import java.util.Objects;
📝 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
public void updateEvent(final String name,
final EventType eventType,
final LocalDateTime startAt,
final LocalDateTime endAt,
final String venue,
final String lineUp,
final String details,
final int minAge,
final LocalDateTime visibleStartAt,
final LocalDateTime visibleEndAt,
final String ticketCheckCode) {
validateDate(startAt, endAt);
validateDate(visibleStartAt, visibleEndAt);
this.name = name;
this.eventType = eventType;
this.startAt = startAt;
this.endAt = endAt;
this.venue = venue;
this.lineUp = lineUp;
this.details = details;
this.minAge = minAge;
this.visibleStartAt = visibleStartAt;
this.visibleEndAt = visibleEndAt;
this.ticketCheckCode = ticketCheckCode;
}
// Add at the top of the file
import java.util.Objects;
public void updateEvent(final String name,
final EventType eventType,
final LocalDateTime startAt,
final LocalDateTime endAt,
final String venue,
final String lineUp,
final String details,
final int minAge,
final LocalDateTime visibleStartAt,
final LocalDateTime visibleEndAt,
final String ticketCheckCode) {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(eventType, "eventType");
Objects.requireNonNull(startAt, "startAt");
Objects.requireNonNull(endAt, "endAt");
Objects.requireNonNull(venue, "venue");
Objects.requireNonNull(visibleStartAt, "visibleStartAt");
Objects.requireNonNull(visibleEndAt, "visibleEndAt");
Objects.requireNonNull(ticketCheckCode, "ticketCheckCode");
if (minAge < 0) {
throw new EventIllegalArgumentException();
}
validateDate(startAt, endAt);
validateDate(visibleStartAt, visibleEndAt);
this.name = name;
this.eventType = eventType;
this.startAt = startAt;
this.endAt = endAt;
this.venue = venue;
this.lineUp = lineUp;
this.details = details;
this.minAge = minAge;
this.visibleStartAt = visibleStartAt;
this.visibleEndAt = visibleEndAt;
this.ticketCheckCode = ticketCheckCode;
}
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java
around lines 83 to 108, the updateEvent method does not guard against null for
required fields nor prevent negative minAge; add null checks (e.g.,
Objects.requireNonNull) for name, eventType, startAt, endAt, venue,
visibleStartAt, visibleEndAt and ticketCheckCode, and validate minAge >= 0
throwing IllegalArgumentException if violated; also add the needed import
java.util.Objects at the top of the file.

Comment on lines +110 to +115
private void validateDate(final LocalDateTime startDate,
final LocalDateTime endDate) {
if (startDate.isAfter(endDate)) {
throw new EventIllegalArgumentException();
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

create()에서도 날짜 검증이 필요합니다 — validateDate를 static+null-safe로 보강하세요.

현재 validateDate가 인스턴스 메서드라 static factory(create)에서 호출할 수 없습니다. static으로 승격하고 null도 방어해 주세요.

동등(start == end) 허용이 도메인 의도인지도 확인 부탁드립니다.

-    private void validateDate(final LocalDateTime startDate,
-                              final LocalDateTime endDate) {
-        if (startDate.isAfter(endDate)) {
-            throw new EventIllegalArgumentException();
-        }
-    }
+    private static void validateDate(final LocalDateTime startDate,
+                                     final LocalDateTime endDate) {
+        if (startDate == null || endDate == null) {
+            throw new EventIllegalArgumentException();
+        }
+        if (startDate.isAfter(endDate)) {
+            throw new EventIllegalArgumentException();
+        }
+    }

🏁 Script executed:

#!/bin/bash
set -e
# Locate EventEntity.java and inspect its content
rg -nP 'class\s+EventEntity' -C3 src
# Find any static create factory methods
rg -nP 'EventEntity\s+create' -C3 src
# Locate validateDate definition
rg -nP 'validateDate' -C3 src

Length of output: 8309


validateDate를 static으로 승격하고 null 방어 추가 필요

  • validateDate가 인스턴스 메서드여서 create()(static)에서 호출 불가하므로 private static으로 변경하고, startDateendDate에 대한 null 체크를 추가하세요.
  • 동등(startDate == endDate) 허용 여부가 도메인 의도인지 다시 확인 바랍니다.
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java
around lines 110 to 115, change validateDate to a private static method and add
null checks for startDate and endDate (throw EventIllegalArgumentException if
either is null), then keep the existing ordering check
(startDate.isAfter(endDate)) to throw EventIllegalArgumentException; also
revisit whether equality (startDate.equals(endDate)) should be allowed and
adjust the comparison accordingly if the domain requires disallowing equal
dates.

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: 0

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/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)

57-68: create(): 날짜 검증 누락 및 파라미터 명 불일치 — 검증 호출 추가 + 이름 통일 권장

엔터티 생성 시 잘못된 기간이 그대로 저장될 수 있습니다. 또한 startDate/endDate 명명은 필드(startAt/endAt)와 불일치합니다. 아래처럼 수정 제안드립니다.

-    public static EventEntity create(final String name,
-                                     final EventType eventType,
-                                     final LocalDateTime startDate,
-                                     final LocalDateTime endDate,
+    public static EventEntity create(final String name,
+                                     final EventType eventType,
+                                     final LocalDateTime startAt,
+                                     final LocalDateTime endAt,
                                      final String venue,
                                      final String lineUp,
                                      final String details,
                                      final int minAge,
                                      final LocalDateTime visibleStartAt,
                                      final LocalDateTime visibleEndAt,
                                      final String ticketCheckCode) {
-
-        return EventEntity.builder()
+        validateDate(startAt, endAt);
+        validateDate(visibleStartAt, visibleEndAt);
+        return EventEntity.builder()
                 .name(name)
                 .eventType(eventType)
-                .startAt(startDate)
-                .endAt(endDate)
+                .startAt(startAt)
+                .endAt(endAt)
                 .venue(venue)
                 .lineUp(lineUp)
                 .details(details)
                 .minAge(minAge)
                 .visibleStartAt(visibleStartAt)
                 .visibleEndAt(visibleEndAt)
                 .ticketCheckCode(ticketCheckCode)
                 .build();
     }

Also applies to: 71-72, 77-78

♻️ Duplicate comments (3)
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (3)

45-46: min_age NOT NULL로 JPA-스키마 정합성 확보 — DB 마이그레이션 반영만 확인 부탁

primitive int와 NOT NULL 제약이 일치합니다. 실제 DB에도 적용되었는지 마이그레이션(예: ALTER TABLE events MODIFY COLUMN min_age INT NOT NULL;) 포함 여부를 확인해 주세요.


83-108: updateEvent(): null 방어 + minAge 음수 금지 + (선택) 가시 기간이 행사 기간 내부인지 보강

부분 업데이트 흐름이라도 이 레벨에서 invariant를 보장하는 편이 안전합니다.

  • 필수값 null 방지: Objects.requireNonNull
  • minAge >= 0 강제
  • (선택) visibleStartAt/visibleEndAtstartAt/endAt 내부인지 확인

아래처럼 보강을 제안드립니다(선택 검증 포함).

     public void updateEvent(final String name,
                             final EventType eventType,
                             final LocalDateTime startAt,
                             final LocalDateTime endAt,
                             final String venue,
                             final String lineUp,
                             final String details,
                             final int minAge,
                             final LocalDateTime visibleStartAt,
                             final LocalDateTime visibleEndAt,
                             final String ticketCheckCode) {
-        validateDate(startAt, endAt);
-        validateDate(visibleStartAt, visibleEndAt);
+        java.util.Objects.requireNonNull(name, "name");
+        java.util.Objects.requireNonNull(eventType, "eventType");
+        java.util.Objects.requireNonNull(startAt, "startAt");
+        java.util.Objects.requireNonNull(endAt, "endAt");
+        java.util.Objects.requireNonNull(venue, "venue");
+        java.util.Objects.requireNonNull(visibleStartAt, "visibleStartAt");
+        java.util.Objects.requireNonNull(visibleEndAt, "visibleEndAt");
+        java.util.Objects.requireNonNull(ticketCheckCode, "ticketCheckCode");
+        if (minAge < 0) {
+            throw new EventIllegalArgumentException();
+        }
+        validateDate(startAt, endAt);
+        validateDate(visibleStartAt, visibleEndAt);
+        // optional: visible 기간이 행사 기간 내부인지 보장
+        if (visibleStartAt.isBefore(startAt) || visibleEndAt.isAfter(endAt)) {
+            throw new EventIllegalArgumentException();
+        }
 
         this.name = name;
         this.eventType = eventType;
         this.startAt = startAt;
         this.endAt = endAt;
         this.venue = venue;
         this.lineUp = lineUp;
         this.details = details;
         this.minAge = minAge;
         this.visibleStartAt = visibleStartAt;
         this.visibleEndAt = visibleEndAt;
         this.ticketCheckCode = ticketCheckCode;
     }

추가로, PATCH 요구사항("바뀐 필드만")과의 정합성 확인을 위해, 호출부가 null을 넘기지 않고 기존 값을 머지한 뒤 이 메서드를 호출하는지 점검 부탁드립니다. 필요 시 아래 스크립트로 호출부를 확인하세요.

#!/bin/bash
# updateEvent 호출부와 Admin 업데이트 플로우 점검
rg -nP -C3 '\bupdateEvent\s*\(' src
rg -nP -C3 'AdminEvent(Update|Updater|Service|Controller)' src

110-115: validateDate: static + null-safe로 보강, 동등 허용 여부 확인 필요

현재 null 시 NPE 위험이 있고, create()에서 재사용이 어렵습니다. 아래처럼 수정하고, 동등(start==end) 허용 정책도 결정해 주세요(불허 시 조건을 !startDate.isBefore(endDate)로 변경).

-    private void validateDate(final LocalDateTime startDate,
-                              final LocalDateTime endDate) {
-        if (startDate.isAfter(endDate)) {
-            throw new EventIllegalArgumentException();
-        }
-    }
+    private static void validateDate(final LocalDateTime startDate,
+                                     final LocalDateTime endDate) {
+        if (startDate == null || endDate == null) {
+            throw new EventIllegalArgumentException();
+        }
+        if (startDate.isAfter(endDate)) {
+            throw new EventIllegalArgumentException();
+        }
+    }
🧹 Nitpick comments (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)

48-52: 가시 기간(visibleStartAt~visibleEndAt)이 행사 기간 내부에 포함되도록 검증 추가 권장

도메인 의도에 부합한다면, 행사 기간 밖으로 벗어나는 가시 기간을 방지하는 체크를 updateEvent()(및 create())에 추가하는 편이 안전합니다. 아래 updateEvent 제안(diff)에 포함했습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 481028c and ab89b5e.

📒 Files selected for processing (1)
  • src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/exception/EventIllegalArgumentException.java (1)
  • EventIllegalArgumentException (3-4)
🔇 Additional comments (1)
src/main/java/com/permitseoul/permitserver/domain/event/core/domain/entity/EventEntity.java (1)

30-34: startAt/endAt 컬럼/필드 명 일관화, 매핑 적절합니다

리네이밍과 nullable = false 제약이 명확합니다.

@sjk4618 sjk4618 merged commit 776e1ab into dev Sep 10, 2025
2 checks passed
@sjk4618 sjk4618 deleted the feat/#123 branch September 10, 2025 16:35
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