Skip to content

Conversation

@sjk4618
Copy link
Member

@sjk4618 sjk4618 commented Oct 15, 2025

🔥Pull requests

⛳️ 작업한 브랜치

👷 작업한 내용

  • feat: admin 타임테이블 최초 등록 api 구현
  • 노션 api로 노션에 있는 데이터베이스와 연동하여 처음 디비에 노션 데이터베이스 정보를 넣음

🚨 참고 사항

@sjk4618 sjk4618 self-assigned this Oct 15, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 15, 2025

📝 Walkthrough

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 관리자를 위한 시간표 초기화 API 추가
    • Notion 데이터 소스 통합으로 시간표, 단계, 카테고리 정보 동기화 지원
  • Refactor

    • 시간표 영역(Area) 개념을 단계(Stage)로 변경
    • 외부 서비스(Google, Kakao) 설정 구조 재조직
  • Chores

    • 보안 정책 업데이트 (API 화이트리스트 추가)

Walkthrough

타임테이블의 Area→Stage 전환과 Notion 연동 도입, 관리자 초기 타임테이블 등록 API 추가 및 외부 연동(Google/Kakao/Notion/Discord) 패키지 재배치와 보안·로깅·오류코드·설정 변경을 포함한 대규모 도메인 및 인프라 변경입니다.

Changes

Cohort / File(s) Summary
애플리케이션 설정 & 외부 연동 재배치
src/main/java/com/permitseoul/permitserver/PermitServerApplication.java, src/main/java/.../global/config/SecurityConfig.java, src/test/java/.../PermitServerApplicationTests.java, src/main/java/.../global/external/google/*, src/main/java/.../global/external/kakao/*, src/main/java/.../global/external/discord/*
Google/Kakao/Discord 관련 클래스 패키지 이동 및 import 변경. NotionProperties 추가 및 @EnableConfigurationProperties 등록. Security whitelist/auth 경로 및 테스트에 캐싱 활성화.
Admin 초기 타임테이블 등록 API
src/main/java/.../domain/admin/timetable/base/api/controller/AdminTimetableController.java, .../dto/req/TimetableInitialPostRequest.java, .../service/AdminTimetableService.java, .../service/AdminTimetableFacade.java, .../core/components/AdminTimetableSaver.java, .../block/core/component/AdminTimetableBlockSaver.java, .../blockmedia/core/component/AdminTimetableBlockMediaSaver.java, .../category/core/component/AdminTimetableCategorySaver.java, .../stage/core/AdminTimetableStageSaver.java
POST 엔드포인트 추가(초기 타임테이블 등록). 요청 DTO 추가. Notion에서 데이터 조회→관계 검증→저장(파사드) 흐름 구현 및 여러 배치 저장 컴포넌트 추가.
Notion 연동 추가
src/main/java/.../global/external/notion/NotionProperties.java, .../NotionProvider.java, .../NotionRelationValidator.java, .../client/NotionClient.java, .../dto/Notion*.java, .../notion/exception/NotFoundNotionResponseException.java
Notion 설정 레코드, Feign 클라이언트, Provider, DTO(타임테이블/스테이지/카테고리) 및 관계 검증기와 Notion 관련 예외 추가.
도메인 변경: Stage 도입 및 Notion ID 반영
src/main/java/.../eventtimetable/stage/.../*, .../category/core/domain/entity/TimetableCategoryEntity.java, .../category/core/repository/TimetableCategoryRepository.java, .../category/core/domain/TimetableCategory.java, .../block/core/domain/entity/TimetableBlockEntity.java, .../block/core/domain/TimetableBlock.java, .../blockmedia/domain/entity/TimetableBlockMediaEntity.java, .../timetable/core/domain/Timetable.java, .../timetable/core/domain/entity/TimetableEntity.java
Stage 엔티티/도메인/레포지토리/리트리버 추가. 카테고리/블록에 Notion row ID(문자열) 추가로 식별자 변경. Timetable 엔티티/도메인에 Notion datasource ID 3종 추가. BlockMedia에 create 팩토리 추가.
Area 관련 제거 및 Timetable 서비스/응답 전환
src/main/java/.../eventtimetable/area/* (삭제), .../timetable/api/service/TimetableService.java, .../timetable/api/dto/TimetableResponse.java
Area 엔티티·리포지토리·리트리버·예외 삭제. TimetableService 및 응답 DTO를 Stage 기반(스테이지 Notion ID 키 사용)으로 전환, 관련 매핑·예외명 변경.
유틸/매퍼/오류코드/기타
src/main/java/.../domain/admin/util/NotionResponseMapper.java, .../domain/admin/util/exception/PermitListSizeNotMatchException.java, .../global/exception/PermitIllegalStateException.java, .../global/response/code/ErrorCode.java, src/main/resources/logback/logback-dev-loki.xml, src/main/java/.../domain/ticket/api/controller/TicketController.java
Notion 응답→엔티티 매퍼 추가. 리스트 크기 불일치/글로벌 IllegalState 예외 추가. 오류코드 추가/명칭 변경 및 메시지 수정. 로깅 Loki endpoint 변경. 티켓 API 경로 변경(/info/{eventId}) 및 시큐리티 화이트리스트 조정. QrCodeUtil은 개행만 변경.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin
  participant Controller as AdminTimetableController
  participant Service as AdminTimetableService
  participant Notion as NotionProvider
  participant Validator as NotionRelationValidator
  participant Facade as AdminTimetableFacade
  participant Savers as Savers (Timetable/Stage/Category/Block/Media)

  Admin->>Controller: POST /api/admin/events/{eventId}/timetables/initial\n(payload: start/end + datasourceIds)
  Controller->>Service: saveInitialTimetableInfo(eventId, ...)
  Service->>Notion: getNotionStageDatasource(id)
  Service->>Notion: getNotionCategoryDatasource(id)
  Service->>Notion: getNotionTimetableDatasource(id)
  Service->>Validator: validateNotionRelationIds(timetable, stage, category)
  Service->>Facade: saveInitialTimetableInfos(eventId, times, ids, notionResponses)
  activate Facade
  Facade->>Savers: saveTimetable()
  Facade->>Savers: saveAllTimetableStages(...)
  Facade->>Savers: saveAllTimetableCategoryEntities(...)
  Facade->>Savers: saveAllTimetableBlocks(...)
  Facade->>Savers: saveAllBlockMedia(...)
  deactivate Facade
  Service-->>Controller: void
  Controller-->>Admin: 200 OK (BaseResponse)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 변경사항 중 일부가 이슈 #157의 주요 목표 범위를 벗어나 보입니다. GoogleProperties/KakaoProperties를 domain.auth.core.external에서 global.external로 패키지 이동하기, DiscordFeignClient 패키지 재구성, TicketController 경로 매핑 변경, SecurityConfig 업데이트 등은 기술적 개선이지만 관리자 타임테이블 등록 API 구현과는 직접적인 관련이 없습니다. 추가로 TimetableArea를 완전히 제거하고 TimetableStage로 교체하는 것은 상당한 리팩토링이며, 이러한 변경들이 새 기능 구현과 명시적으로 연결되지 않습니다. PR 범위를 명확히 하기 위해 다음이 권장됩니다: (1) 노션 통합에 필수적이지 않은 Google/Kakao 속성 패키지 재배치, Discord 클라이언트 패키지 구조 변경, TicketController 경로 변경과 같은 변경사항을 별도 PR로 분리하기, (2) TimetableArea에서 TimetableStage로의 전환이 타임테이블 등록 API 구현에 필수적인 경우, 이 의존성을 PR 설명이나 코멘트에서 명확히 문서화하기, (3) 범위가 증가한 경우 이슈 또는 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 구현 - #157"은 변경사항의 주요 목표를 명확하게 설명합니다. PR의 핵심 기능인 관리자 타임테이블 최초 등록 API 구현을 정확히 반영하고 있으며, 관례적인 커밋 형식(feat: 접두사)을 따르고 있습니다. 제목은 간결하고 구체적이며, 변경사항 히스토리를 스캔할 때 팀원이 주요 변화를 명확히 이해할 수 있습니다.
Linked Issues Check ✅ Passed 이슈 #157의 요구사항은 "admin 타임테이블 등록 API 구현"과 "노션 API 연동을 통한 데이터베이스 연동"입니다. 변경사항에는 AdminTimetableController, AdminTimetableService, AdminTimetableFacade 및 관련 저장소 컴포넌트, NotionProvider, NotionClient, NotionRelationValidator 등의 노션 통합 구성요소가 포함되어 있습니다. 또한 요청/응답 DTO와 필요한 도메인 엔티티 업데이트가 포함되어 있어, PR이 명시된 요구사항을 충족합니다.
Description Check ✅ Passed PR 설명은 작업 내용과 관련이 있습니다. 관리자 타임테이블 최초 등록 API 구현과 노션 API 연동을 통해 노션 데이터베이스 정보를 서버 DB에 입력하는 기능에 대해 명시하고 있으며, 이는 변경사항의 핵심을 정확히 나타냅니다. 설명의 세부 수준은 이 검사에서 중요하지 않으므로 전반적으로 변경사항과 충분히 관련이 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#157

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

🧹 Nitpick comments (14)
src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordDevHttpLogSender.java (1)

19-19: 옵션: ObjectMapper 빈 주입 고려

현재 static ObjectMapper는 정상적으로 작동하지만 (Jackson의 ObjectMapper는 읽기 작업에 대해 스레드 안전함), 테스트 용이성과 설정 커스터마이징을 위해 Spring 빈으로 주입하는 것을 고려할 수 있습니다.

src/main/java/com/permitseoul/permitserver/domain/ticket/api/controller/TicketController.java (1)

24-29: PR 범위와의 연관성 확인 필요

이 변경사항은 티켓 정보 조회 API의 경로 수정에 관한 것인데, PR 제목과 설명은 "admin 타임테이블 최초 등록 API 구현"으로 되어 있습니다.

  • 이 변경사항이 타임테이블 기능 구현의 일부인 경우, PR 설명에 티켓 API 경로 변경에 대한 내용을 추가하는 것이 좋습니다
  • 만약 관련이 없는 변경사항이라면, 별도의 PR로 분리하여 변경 이력을 명확히 하는 것을 권장합니다
src/main/java/com/permitseoul/permitserver/global/external/kakao/client/KakaoKAuthClient.java (1)

11-17: 폼 인코딩 명시로 상호운용성 강화 제안

Kakao 토큰 엔드포인트는 x-www-form-urlencoded를 기대합니다. consumes를 명시해 예측 가능성을 높이는 것을 권장합니다.

-    @PostMapping("/oauth/token")
+    @PostMapping(value = "/oauth/token", consumes = "application/x-www-form-urlencoded")
     KakaoAccessTokenResponse getKakaoAccessToken(
         @RequestParam("grant_type") String grantType,
         @RequestParam("client_id") String clientId,
         @RequestParam("redirect_uri") String redirectUri,
         @RequestParam("code") String code
     );
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/entity/TimetableStageEntity.java (1)

25-29: sequence 제약 및 조회 인덱스 권장

음수 방지를 위한 @min(0) 및 조회 빈번 컬럼(timetable_id, notion_stage_row_id) 인덱스를 권장합니다.

예시:

// 상단 import
import jakarta.validation.constraints.Min;
import jakarta.persistence.Index;

@Table(name = "event_timetable_stage",
       indexes = {
         @Index(name = "idx_event_timetable_stage_tid", columnList = "timetable_id"),
         @Index(name = "uk_event_timetable_stage_notion_id", columnList = "notion_stage_row_id", unique = true)
})
// ...
@Min(0)
@Column(name = "sequence", nullable = false)
private int sequence;
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/TimetableCategory.java (1)

15-25: 식별자 필드 네이밍 일관성 제안

도메인은 categoryNotionId, 엔티티는 notionCategoryRowId로 상반된 접두/접미어 순서를 사용합니다. 유지보수성을 위해 동일한 네이밍(not ionCategoryRowId 또는 notionCategoryId 등)으로 통일을 권장합니다.

이 필드가 외부 API 응답/요청에 노출된다면 변경 영향도를 확인해 주세요.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/exception/TimetableStageCoreException.java (1)

3-4: 표준 예외 생성자 추가를 권장합니다.

기본 추상 클래스 구조는 적절하지만, 에러 메시지와 원인을 전달할 수 있는 표준 생성자들을 추가하면 더 유연한 예외 처리가 가능합니다.

다음과 같이 생성자를 추가하는 것을 고려해보세요:

 public abstract class TimetableStageCoreException extends RuntimeException {
+    protected TimetableStageCoreException() {
+        super();
+    }
+
+    protected TimetableStageCoreException(String message) {
+        super(message);
+    }
+
+    protected TimetableStageCoreException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    protected TimetableStageCoreException(Throwable cause) {
+        super(cause);
+    }
 }
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/category/core/component/AdminTimetableCategorySaver.java (1)

10-18: 일관된 구조로 잘 구현되었습니다.

배치 저장 컴포넌트가 AdminTimetableStageSaver와 일관된 패턴으로 구현되었습니다.

메서드 이름을 간소화하는 것을 고려해보세요:

-    public void saveAllTimetableCategoryEntities(final List<TimetableCategoryEntity> timetableCategoryEntities) {
+    public void saveAll(final List<TimetableCategoryEntity> timetableCategoryEntities) {
         timetableCategoryRepository.saveAll(timetableCategoryEntities);
     }

컴포넌트 이름이 이미 역할을 명확히 하므로 메서드 이름에서 "TimetableCategoryEntities" 반복을 줄일 수 있습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (1)

3-3: 사용하지 않는 import를 제거하세요.

Line 3의 TimetableBlockEntity import는 코드에서 사용되지 않습니다.

다음 diff를 적용하여 불필요한 import를 제거하세요:

-import com.permitseoul.permitserver.domain.eventtimetable.block.core.domain.entity.TimetableBlockEntity;
 import com.permitseoul.permitserver.domain.eventtimetable.blockmedia.domain.entity.TimetableBlockMediaEntity;
src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProvider.java (1)

17-21: 헤더 주입은 Feign RequestInterceptor로 통합 권장

토큰/버전/컨텐츠 타입을 메서드마다 인자로 전달하지 말고, RequestInterceptor에서 공통 헤더로 주입하면 중복이 줄고 안전합니다. 또한 NotionProperties의 값(@notblank 등) 유효성 검증도 함께 구성해 주세요.

예시:

@Bean
public RequestInterceptor notionAuthInterceptor(NotionProperties props) {
    return tpl -> {
        tpl.header("Authorization", "Bearer " + props.privateApiToken());
        tpl.header("Notion-Version", props.notionVersion());
        tpl.header("Content-Type", MediaType.APPLICATION_JSON_VALUE);
    };
}
src/main/java/com/permitseoul/permitserver/global/external/notion/dto/NotionStageDatasourceResponse.java (1)

1-8: DTO 역직렬화 탄력성 확보를 위해 unknown 필드 무시 설정 권장

Notion 응답 필드가 추가되어도 파싱 실패가 없도록 ignoreUnknown을 추가해 주세요. 다른 Notion DTO에도 동일 적용을 권장합니다.

 package com.permitseoul.permitserver.global.external.notion.dto;

 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import java.util.List;

-public record NotionStageDatasourceResponse(
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record NotionStageDatasourceResponse(
         List<StagePage> results
 ) {
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableService.java (2)

22-26: 미사용 의존성 제거

AdminTimetableStageSaver 필드는 이 클래스에서 사용되지 않습니다. 제거해 주세요.


27-33: 시간 유효성 검증 추가 권장

timetableStartAt이 timetableEndAt 이후인 경우를 사전에 차단해 주세요. 잘못된 데이터 저장을 방지합니다.

예시:

if (!timetableStartAt.isBefore(timetableEndAt)) {
    throw new AdminApiException(ErrorCode.INVALID_REQUEST);
}
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (1)

25-29: Notion ID 검증 권장

timetableCategoryNotionId와 timetableStageNotionId가 String 타입으로 변경되었습니다. 이 필드들이 null이나 빈 문자열이 될 수 없도록 생성자나 팩토리 메서드에서 검증을 추가하는 것을 권장합니다.

생성자에 검증 로직을 추가할 수 있습니다:

     private TimetableBlockEntity(
             long timetableId,
             String timetableCategoryNotionId,
             String timetableStageNotionId,
             LocalDateTime startAt,
             LocalDateTime endAt,
             String blockName,
             String artist,
             String information,
             String blockInfoRedirectUrl
     ) {
         validateDateTime(startAt, endAt);
+        validateNotionIds(timetableCategoryNotionId, timetableStageNotionId);
 
         this.timetableId = timetableId;
         this.timetableCategoryNotionId = timetableCategoryNotionId;
         this.timetableStageNotionId = timetableStageNotionId;
         this.startAt = startAt;
         this.endAt = endAt;
         this.blockName = blockName;
         this.artist = artist;
         this.information = information;
         this.blockInfoRedirectUrl = blockInfoRedirectUrl;
     }
+
+    private void validateNotionIds(final String categoryNotionId, final String stageNotionId) {
+        if (categoryNotionId == null || categoryNotionId.isBlank()) {
+            throw new IllegalArgumentException("Category Notion ID cannot be null or empty");
+        }
+        if (stageNotionId == null || stageNotionId.isBlank()) {
+            throw new IllegalArgumentException("Stage Notion ID cannot be null or empty");
+        }
+    }
src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (1)

59-72: 기본 색상값 검토

backgroundColor와 lineColor가 비어 있을 경우 빈 문자열("")로 API 응답에 반영됩니다. UI 렌더링이나 클라이언트 로직에서 유효한 CSS 색상값을 기대한다면, 기본 색상(예: "#FFFFFF")을 지정하거나 null 처리 로직을 추가하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83bca4f and b943ca8.

📒 Files selected for processing (60)
  • src/main/java/com/permitseoul/permitserver/PermitServerApplication.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/controller/AdminTimetableController.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/dto/req/TimetableInitialPostRequest.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableService.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/core/components/AdminTimetableSaver.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/block/core/component/AdminTimetableBlockSaver.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/category/core/component/AdminTimetableCategorySaver.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/stage/core/AdminTimetableStageSaver.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/util/QrCodeUtil.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/auth/core/strategy/GoogleLoginStrategy.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/auth/core/strategy/KakaoLoginStrategy.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/component/TimetableAreaRetriever.java (0 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/domain/TimetableArea.java (0 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/domain/entity/TimetableAreaEntity.java (0 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/exception/TimetableAreaCoreException.java (0 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/exception/TimetableAreaNotFoundException.java (0 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/repository/TimetableAreaRepository.java (0 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/blockmedia/domain/entity/TimetableBlockMediaEntity.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/component/TimetableCategoryRetriever.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/TimetableCategory.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/entity/TimetableCategoryEntity.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/repository/TimetableCategoryRepository.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/component/TimetableStageRetriever.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/TimetableStage.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/entity/TimetableStageEntity.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/exception/TimetableStageCoreException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/exception/TimetableStageNotFoundException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/repository/TimetableStageRepository.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/dto/TimetableResponse.java (3 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/service/TimetableService.java (8 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 (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/ticket/api/controller/TicketController.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/config/SecurityConfig.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/global/exception/PermitIllegalStateException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordDevHttpLogSender.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/discord/client/DiscordFeignClient.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/google/GoogleProperties.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/google/client/GoogleClient.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/google/dto/GoogleTokenResponse.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/kakao/KakaoProperties.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/kakao/client/KakaoKApiClient.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/kakao/client/KakaoKAuthClient.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/kakao/dto/KakaoATInfoResponse.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/kakao/dto/KakaoAccessTokenResponse.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProperties.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProvider.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/NotionRelationValidator.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/client/NotionClient.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/dto/NotionCategoryDatasourceResponse.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/dto/NotionStageDatasourceResponse.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/dto/NotionTimetableDatasourceResponse.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java (2 hunks)
  • src/main/resources/logback/logback-dev-loki.xml (1 hunks)
  • src/test/java/com/permitseoul/permitserver/PermitServerApplicationTests.java (1 hunks)
💤 Files with no reviewable changes (6)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/exception/TimetableAreaNotFoundException.java
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/domain/entity/TimetableAreaEntity.java
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/domain/TimetableArea.java
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/component/TimetableAreaRetriever.java
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/repository/TimetableAreaRepository.java
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/area/core/exception/TimetableAreaCoreException.java
🧰 Additional context used
🧬 Code graph analysis (15)
src/main/java/com/permitseoul/permitserver/global/external/notion/NotionRelationValidator.java (1)
src/main/java/com/permitseoul/permitserver/global/exception/PermitIllegalStateException.java (1)
  • PermitIllegalStateException (3-4)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (3)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (1)
  • Component (27-69)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/core/components/AdminTimetableSaver.java (1)
  • Component (11-32)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/block/core/component/AdminTimetableBlockSaver.java (1)
  • Component (11-21)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/category/core/component/AdminTimetableCategorySaver.java (1)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/stage/core/AdminTimetableStageSaver.java (1)
  • Component (10-18)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/core/components/AdminTimetableSaver.java (3)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (1)
  • Component (27-69)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/block/core/component/AdminTimetableBlockSaver.java (1)
  • Component (11-21)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (1)
  • Component (11-19)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/component/TimetableStageRetriever.java (4)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/exception/TimetableStageNotFoundException.java (1)
  • TimetableStageNotFoundException (3-4)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/TimetableStage.java (1)
  • RequiredArgsConstructor (7-26)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/service/TimetableService.java (1)
  • RequiredArgsConstructor (35-195)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/component/TimetableCategoryRetriever.java (1)
  • Component (13-36)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/controller/AdminTimetableController.java (1)
src/main/java/com/permitseoul/permitserver/global/response/ApiResponseUtil.java (1)
  • ApiResponseUtil (7-29)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (4)
src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (1)
  • NotionResponseMapper (19-142)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/core/components/AdminTimetableSaver.java (1)
  • Component (11-32)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/block/core/component/AdminTimetableBlockSaver.java (1)
  • Component (11-21)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (1)
  • Component (11-19)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/block/core/component/AdminTimetableBlockSaver.java (3)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (1)
  • Component (27-69)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/core/components/AdminTimetableSaver.java (1)
  • Component (11-32)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (1)
  • Component (11-19)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/TimetableStage.java (3)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/TimetableBlock.java (1)
  • Getter (9-37)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/TimetableCategory.java (1)
  • RequiredArgsConstructor (7-28)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/service/TimetableService.java (1)
  • RequiredArgsConstructor (35-195)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/entity/TimetableStageEntity.java (4)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/TimetableBlock.java (1)
  • Getter (9-37)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/entity/TimetableBlockEntity.java (1)
  • Entity (11-91)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/blockmedia/domain/entity/TimetableBlockMediaEntity.java (1)
  • Entity (10-41)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/entity/TimetableCategoryEntity.java (1)
  • Entity (8-53)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/component/TimetableCategoryRetriever.java (1)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/exception/TimetableCategoryNotfoundException.java (1)
  • TimetableCategoryNotfoundException (3-4)
src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProvider.java (1)
src/main/java/com/permitseoul/permitserver/global/Constants.java (1)
  • Constants (3-24)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/stage/core/AdminTimetableStageSaver.java (1)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/category/core/component/AdminTimetableCategorySaver.java (1)
  • Component (10-18)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableService.java (2)
src/main/java/com/permitseoul/permitserver/global/exception/PermitIllegalStateException.java (1)
  • PermitIllegalStateException (3-4)
src/main/java/com/permitseoul/permitserver/global/external/notion/NotionRelationValidator.java (1)
  • NotionRelationValidator (11-40)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/service/TimetableService.java (1)
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/exception/TimetableStageNotFoundException.java (1)
  • TimetableStageNotFoundException (3-4)
🔇 Additional comments (44)
src/main/resources/logback/logback-dev-loki.xml (1)

6-6: 호스트 변경 동작 확인 필요

dev 환경에서 http://loki:3100 호스트명이 실제로 DNS 해석되고 접근 가능한지 다시 확인 부탁드립니다. 기존 localhost 대비 네트워크 경로가 달라 잠재적 접속 실패 가능성이 있습니다.

src/main/java/com/permitseoul/permitserver/global/external/discord/client/DiscordFeignClient.java (1)

1-1: 패키지 재구성 승인

Discord 관련 클라이언트를 client 서브패키지로 분리한 것은 코드 구조를 개선하는 좋은 변경입니다. 관련 의존성들이 모두 업데이트되었습니다.

src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordDevHttpLogSender.java (1)

5-5: LGTM!

패키지 재구성에 따른 import 경로 업데이트가 정확합니다.

src/main/java/com/permitseoul/permitserver/domain/ticket/api/controller/TicketController.java (1)

24-24: 엔드포인트 /api/tickets/{eventId}/api/tickets/info/{eventId}로 변경됨
SecurityConfig에서 이미 /api/tickets/info/*로 갱신된 것은 확인했습니다. 프론트엔드·모바일 앱 등 외부 클라이언트와 API 문서(Swagger 등)가 새 경로로 모두 반영되었는지 검증해 주세요.

src/main/java/com/permitseoul/permitserver/global/config/SecurityConfig.java (2)

56-56: 인증 범위 변경 검토 필요

SecurityConfig에서 /api/tickets/**/api/tickets/user로 변경되어 아래 엔드포인트가 인증 제외되었습니다.

  • GET /api/tickets/info/{eventId}
  • POST /api/tickets/confirm

해당 경로들이 의도적으로 공개된 것이 맞는지, 혹은 인증이 필요하다면 설정을 조정해주세요.


38-38: 패턴 중복 아님
/api/events/*/timetables은 이벤트 ID로 타임테이블 목록 조회, /api/events/timetables/*은 블록 ID로 상세 조회에 매핑되어 있어 중복이 아닙니다.

Likely an incorrect or invalid review comment.

src/main/java/com/permitseoul/permitserver/global/external/kakao/client/KakaoKApiClient.java (1)

1-14: 패키지 정리/이관 LGTM

동작/시그니처 변화 없이 패키지·임포트만 정리된 것으로 보이며 문제 없습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/timetable/block/core/component/AdminTimetableBlockSaver.java (1)

16-20: LGTM

일괄 저장 후 도메인 매핑 흐름이 명확합니다. 트랜잭션은 상위 Facade @transactional 범위에서 커버되므로 현재 형태로 충분합니다.

src/main/java/com/permitseoul/permitserver/global/external/kakao/KakaoProperties.java (1)

1-9: 패키지 이관 LGTM

프로퍼티 프리픽스/레코드 구조 그대로 유지되어 호환성 문제 없어 보입니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/entity/TimetableStageEntity.java (1)

8-10: 일관된 테이블/컬럼 네이밍 사용

  • @Table(name = "event_timetable_stages")@Table(name = "event_timetable_stage")
  • 외래키 어노테이션(@JoinColumn(name = "event_timetable_id")) → @JoinColumn(name = "timetable_id")으로 변경
  • 마이그레이션/DDL에서도 동일한 네이밍이 적용되었는지 확인 바랍니다.
src/test/java/com/permitseoul/permitserver/PermitServerApplicationTests.java (1)

4-5: 패키지 재구성과 캐싱 설정이 적절합니다.

Google과 Kakao 속성 클래스의 import 경로가 global.external 패키지로 일관되게 업데이트되었고, 테스트에서 @EnableCaching을 활성화하여 캐시 의존 기능을 테스트할 수 있도록 했습니다.

Also applies to: 9-9, 11-11

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

27-28: Notion ID 필드명 변경이 일관되게 적용되었습니다.

fromEntity 메서드가 새로운 Notion ID 필드명을 올바르게 사용하도록 업데이트되었습니다.

src/main/java/com/permitseoul/permitserver/domain/auth/core/strategy/KakaoLoginStrategy.java (1)

6-9: 패키지 재구성이 일관되게 적용되었습니다.

Kakao 관련 클래스들의 import 경로가 global.external.kakao 패키지로 올바르게 업데이트되었으며, 로직 변경은 없습니다.

src/main/java/com/permitseoul/permitserver/global/exception/PermitIllegalStateException.java (1)

1-4: 마커 예외 클래스가 적절하게 구현되었습니다.

PermitIllegalStateException은 Notion 관계 검증에서 사용되는 도메인 특화 예외입니다. 추가 동작 없이 PermitGlobalException을 상속하는 마커 예외 패턴은 적절합니다.

src/main/java/com/permitseoul/permitserver/global/external/google/GoogleProperties.java (1)

1-1: 패키지 재구성이 적절합니다.

GooglePropertiesglobal.external.google 패키지로 이동되어 외부 통합 구성을 전역 패키지에 일관되게 배치했습니다.

src/main/java/com/permitseoul/permitserver/global/external/google/client/GoogleClient.java (1)

1-1: 패키지 재구성이 일관되게 적용되었습니다.

GoogleClient와 관련 DTO의 import가 global.external.google 네임스페이스로 올바르게 업데이트되었습니다. 기능적 변경은 없습니다.

Also applies to: 3-3

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/exception/TimetableStageNotFoundException.java (1)

1-4: 도메인 특화 예외가 적절하게 구현되었습니다.

TimetableStageNotFoundException은 타임테이블 스테이지 조회 실패 시 사용되는 명확한 의미의 예외입니다. TimetableStageCoreException을 상속하는 구조가 적절합니다.

src/main/java/com/permitseoul/permitserver/global/external/google/dto/GoogleTokenResponse.java (1)

1-1: 패키지 재구성이 적절합니다.

GoogleTokenResponseglobal.external.google.dto 패키지로 이동되어 DTO 클래스들을 일관된 위치에 배치했습니다.

src/main/java/com/permitseoul/permitserver/domain/auth/core/strategy/GoogleLoginStrategy.java (1)

9-10: LGTM!

Google 통합 클래스들의 패키지 경로가 global.external.google로 정상적으로 재구성되었습니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/repository/TimetableCategoryRepository.java (1)

14-14: LGTM!

Notion 행 ID 기반 조회 메서드가 올바르게 추가되었습니다. Spring Data JPA 명명 규칙을 준수하고 있으며, Optional 반환 타입이 적절합니다.

src/main/java/com/permitseoul/permitserver/global/external/kakao/dto/KakaoAccessTokenResponse.java (1)

1-1: LGTM!

Kakao DTO의 패키지 경로가 global.external.kakao.dto로 적절하게 재구성되었습니다.

src/main/java/com/permitseoul/permitserver/global/external/kakao/dto/KakaoATInfoResponse.java (1)

1-1: LGTM!

Kakao DTO의 패키지 경로가 global.external.kakao.dto로 일관되게 재구성되었습니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/blockmedia/domain/entity/TimetableBlockMediaEntity.java (1)

38-40: LGTM!

팩토리 메서드가 올바르게 추가되었습니다. 생성 로직을 중앙화하고 private 생성자로 캡슐화를 유지하고 있습니다.

src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProperties.java (1)

5-9: 민감한 Notion API 토큰 관리 검증 필요

  • .properties, .yml, .yaml 파일에서 토큰 하드코딩 항목이 발견되지 않았습니다.
  • 환경 변수 기반 설정 및 로그 마스킹 적용 여부를 수동으로 검토해주세요.
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (1)

1-19: LGTM! 일관된 패턴을 따릅니다.

다른 Saver 컴포넌트들(AdminTimetableBlockSaver, AdminTimetableCategorySaver)과 동일한 패턴으로 구현되어 있습니다. 배치 저장 로직이 올바르게 구현되었습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/controller/AdminTimetableController.java (1)

1-35: LGTM! RESTful 설계가 잘 되어 있습니다.

컨트롤러 구현이 적절합니다:

  • RESTful 엔드포인트 설계 (POST /api/admin/events/{eventId}/timetables/initial)
  • @Valid 어노테이션으로 요청 검증 활성화
  • 표준화된 응답 형식 사용 (ApiResponseUtil.success)
  • 비즈니스 로직을 서비스 계층에 위임
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/stage/core/AdminTimetableStageSaver.java (1)

1-18: LGTM! 다른 Saver 컴포넌트와 일관성이 있습니다.

AdminTimetableCategorySaver와 동일한 패턴으로 구현되어 배치 저장 로직이 명확하고 일관성 있게 작성되었습니다.

src/main/java/com/permitseoul/permitserver/PermitServerApplication.java (2)

8-9: LGTM! 패키지 구조 개선입니다.

외부 통합 관련 Properties 클래스들을 global.external 패키지로 이동한 것은 합리적인 리팩토링입니다. GoogleProperties와 KakaoProperties를 global 패키지로 이동하여 도메인 계층과 분리했습니다.

Also applies to: 13-13


30-31: LGTM! Notion 통합을 위한 설정 추가.

NotionProperties를 @EnableConfigurationProperties에 추가하여 Notion API 통합을 지원합니다. 다른 외부 Properties들과 일관성 있게 등록되었습니다.

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

17-19: LGTM! Notion 데이터소스 ID 필드 추가.

Timetable 도메인 모델에 Notion 데이터소스 식별자를 추가하여 Notion 통합을 지원합니다. fromEntity 메서드에서도 새 필드들을 올바르게 매핑하고 있습니다.

Also applies to: 26-29

src/main/java/com/permitseoul/permitserver/global/external/notion/NotionRelationValidator.java (1)

11-39: LGTM! Notion 관계 검증 로직이 올바릅니다.

Notion 데이터소스 간의 관계 ID를 검증하는 로직이 정확하게 구현되었습니다:

  • Stage와 Category의 유효한 ID Set을 생성
  • Timetable의 관계 ID들이 유효한 Set에 포함되는지 검증
  • 유효하지 않은 관계가 발견되면 PermitIllegalStateException 발생

Stream API를 적절히 사용하여 검증 로직이 명확합니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/repository/TimetableStageRepository.java (1)

11-16: LGTM! Repository 메서드 정의가 적절합니다.

Spring Data JPA의 쿼리 메서드 네이밍 규칙을 따라 findAllByTimetableIdfindByNotionStageRowId 메서드가 올바르게 선언되었습니다.

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

30-37: LGTM! Notion 데이터소스 ID 컬럼 추가.

새로운 Notion 데이터소스 식별자 컬럼들이 올바르게 정의되었습니다:

  • nullable=false로 데이터 무결성 보장
  • length=100은 Notion ID에 적절한 길이
  • 명확한 컬럼명 사용

39-71: LGTM! 생성자와 팩토리 메서드가 일관되게 업데이트되었습니다.

private 생성자와 public static create 메서드가 새로운 Notion 데이터소스 ID 파라미터를 포함하여 일관되게 확장되었습니다. 모든 필드 할당이 올바르게 수행됩니다.

src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProvider.java (1)

23-48: 위임 메서드 구성은 적절합니다

헤더/버전/컨텐츠 타입 전달과 클라이언트 위임 구조가 명확합니다.

src/main/java/com/permitseoul/permitserver/global/external/notion/dto/NotionStageDatasourceResponse.java (1)

16-19: data_source_id 사용이 최신 Notion API 응답 스키마와 일치합니다.
API 버전 2025-09-03 기준 Parent 객체는 "data_source_id" 필드를 반환하므로 database_id로 변경할 필요가 없습니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/dto/TimetableResponse.java (2)

27-35: 변경 사항 확인 완료

Area에서 Stage로의 네이밍 변경과 함께 areaId(long)에서 stageNotionId(String)로 타입 변경이 적절하게 이루어졌습니다. Notion 기반 식별자로의 전환이 일관되게 적용되었습니다.


46-46: 변경 사항 확인 완료

Block 레코드의 blockAreaId(long)에서 blockStageNotionId(String)로의 변경이 적절합니다. Stage 기반 Notion ID 참조로 일관되게 업데이트되었습니다.

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/domain/TimetableStage.java (1)

1-26: 구현 확인 완료

TimetableStage 도메인 객체가 적절하게 구현되었습니다. 불변 객체 패턴과 엔티티 변환 팩토리 메서드가 프로젝트의 다른 도메인 객체들(TimetableBlock, TimetableCategory)과 일관된 방식으로 작성되었습니다.

src/main/java/com/permitseoul/permitserver/global/external/notion/dto/NotionCategoryDatasourceResponse.java (1)

1-48: DTO 구조 확인 완료

NotionCategoryDatasourceResponse가 Notion API 응답 구조를 적절하게 반영하고 있습니다. 중첩된 레코드 구조와 Jackson 애노테이션 매핑이 올바르게 작성되었습니다.

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

105-106: Notion ID 기반 조회로 변경 확인 완료

카테고리와 스테이지 조회가 Notion ID 기반으로 변경되었습니다. TimetableBlock의 Notion ID 필드와 일관되게 작동합니다.


148-194: 매핑 메서드 업데이트 확인 완료

카테고리 색상 맵이 Notion ID 기반(String)으로 변경되었고, Stage 매핑 메서드가 추가되었으며, 블록 매핑이 Notion ID를 사용하도록 업데이트되었습니다. 모든 변경사항이 일관되게 적용되었습니다.

src/main/java/com/permitseoul/permitserver/global/external/notion/dto/NotionTimetableDatasourceResponse.java (1)

1-89: Notion DTO 구조 확인 완료

NotionTimetableDatasourceResponse가 Notion API의 복잡한 응답 구조를 적절하게 표현하고 있습니다. 중첩된 레코드 구조와 Jackson 애노테이션이 올바르게 작성되었으며, 한국어 주석으로 각 필드 타입이 명확하게 문서화되어 있습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (1)

35-39: 시퀀스 기본값(-1) 검증 필요
result.properties().sequence()null일 때 -1을 할당하고 있습니다. 이 기본값이 정렬 및 다른 처리 로직에서 의도대로 작동하는지 확인하세요.

Comment on lines +16 to +21
public Timetable saveTimetable(final long eventId,
final LocalDateTime timetableStartAt,
final LocalDateTime timetableEndAt,
final String notionTimetableDataSourceId,
final String notionTimetableStageDataSourceId,
final String notionTimetableCategoryDataSourceId) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

파라미터 순서 혼동 위험 및 실제 호출부에서 순서 오류 감지됨

AdminTimetableFacade에서 saveTimetable 호출 시 stage/category ID 순서가 뒤바뀌어 전달되고 있습니다. 즉시 정정 필요. 또한 순서 오류를 방지하려면 커맨드 객체(예: TimetableCreateCommand)로 묶는 것을 권장합니다.

호출부 점검 스크립트:

권장 호출 형태(예시):

adminTimetableSaver.saveTimetable(
    eventId,
    timetableStartAt,
    timetableEndAt,
    notionTimetableDataSourceId,
    notionTimetableStageDataSourceId,
    notionTimetableCategoryDataSourceId
);

장기적으로는:

public record TimetableCreateCommand(
    long eventId,
    LocalDateTime startAt,
    LocalDateTime endAt,
    String notionTimetableDataSourceId,
    String notionStageDataSourceId,
    String notionCategoryDataSourceId
) {}

🏁 Script executed:

#!/bin/bash
# saveTimetable 호출부와 인자 순서 확인
rg -nP -C3 --type=java '\bsaveTimetable\s*\('

Length of output: 2364


🏁 Script executed:

#!/bin/bash
sed -n '45,65p' src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java

Length of output: 1225


인자 순서 오류: stage↔category ID 교환 필요

  • AdminTimetableFacade.java:47 saveTimetable 호출부에서 5번째 인자를 notionStageDataSourceId, 6번째 인자를 notionCategoryDataSourceId로 교정하세요.
  • 파라미터 혼동 방지를 위해 TimetableCreateCommand 레코드로 묶는 리팩토링을 권장합니다.
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/core/components/AdminTimetableSaver.java
around lines 16-21, the method parameter order for stage and category data
source IDs is reversed relative to the caller (AdminTimetableFacade.java:47).
Fix this by swapping the last two parameters so the signature becomes (...
String notionTimetableCategoryDataSourceId, String
notionTimetableStageDataSourceId) to match call-site argument order, and update
all internal usages accordingly; alternatively, refactor by introducing a
TimetableCreateCommand record that groups these parameters (eventId,
timetableStartAt, timetableEndAt, notionTimetableDataSourceId,
notionTimetableStageDataSourceId, notionTimetableCategoryDataSourceId) and
change the method to accept that record to avoid future ordering mistakes.

Comment on lines +125 to +138
for (int i = 0; i < savedBlocks.size(); i++) {
final TimetableBlock block = savedBlocks.get(i);
final List<NotionTimetableDatasourceResponse.FilesProperty.FileItem> files = notionTimetableDatasourceResponse.results().get(i).properties().media().files();
if (files == null) continue;

for (int seq = 0; seq < files.size(); seq++) {
String mediaUrl = files.get(seq).file().url();
mediaEntities.add(TimetableBlockMediaEntity.create(
block.getTimetableBlockId(),
seq,
mediaUrl
));
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

리스트 동기화 위험 - 중요

savedBlocks와 notionTimetableDatasourceResponse.results()의 크기와 순서가 일치한다고 가정하고 있습니다. 이는 매우 위험한 가정입니다:

  1. 두 리스트의 크기가 다르면 IndexOutOfBoundsException이 발생합니다.
  2. 순서가 일치하지 않으면 잘못된 블록에 미디어가 연결됩니다.
  3. 저장 과정에서 일부 블록이 실패하거나 필터링되면 인덱스가 맞지 않게 됩니다.

Notion ID를 키로 사용하여 매칭하도록 수정하세요:

     public static List<TimetableBlockMediaEntity> mapToTimetableBlockMediaEntities(final List<TimetableBlock> savedBlocks,
                                                                                    final NotionTimetableDatasourceResponse notionTimetableDatasourceResponse) {
         final List<TimetableBlockMediaEntity> mediaEntities = new ArrayList<>();
+        
+        // Notion ID를 키로 하는 맵 생성
+        final Map<String, TimetableBlock> blocksByNotionId = savedBlocks.stream()
+            .collect(Collectors.toMap(
+                block -> /* Notion ID 추출 로직 필요 */,
+                block -> block
+            ));
 
-        for (int i = 0; i < savedBlocks.size(); i++) {
-            final TimetableBlock block = savedBlocks.get(i);
-            final List<NotionTimetableDatasourceResponse.FilesProperty.FileItem> files = notionTimetableDatasourceResponse.results().get(i).properties().media().files();
+        for (final NotionTimetableDatasourceResponse.NotionPage page : notionTimetableDatasourceResponse.results()) {
+            final String notionId = /* page에서 ID 추출 */;
+            final TimetableBlock block = blocksByNotionId.get(notionId);
+            if (block == null) continue; // 매칭되는 블록이 없으면 스킵
+            
+            final List<NotionTimetableDatasourceResponse.FilesProperty.FileItem> files = page.properties().media().files();
             if (files == null) continue;
 
             for (int seq = 0; seq < files.size(); seq++) {
-                String mediaUrl = files.get(seq).file().url();
+                final String mediaUrl = files.get(seq).file().url();
+                if (mediaUrl == null || mediaUrl.isEmpty()) continue;
                 mediaEntities.add(TimetableBlockMediaEntity.create(
                         block.getTimetableBlockId(),
                         seq,
                         mediaUrl
                 ));
             }
         }
 
         return mediaEntities;
     }

참고: TimetableBlock에 Notion ID가 포함되어 있지 않은 것으로 보입니다. 이 경우 매핑 로직을 재검토하거나 TimetableBlock 도메인 객체에 Notion ID를 추가해야 할 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java
around lines 125 to 138, the code assumes savedBlocks and
notionTimetableDatasourceResponse.results() are same size and order, which can
cause IndexOutOfBounds or incorrect mappings; fix by matching on a stable key
(Notion ID): ensure TimetableBlock contains the Notion ID (or add it), build a
Map from notion results keyed by their Notion ID, then iterate savedBlocks and
for each block lookup the corresponding notion result by block.getNotionId();
skip or log if no match, guard against null files, and create media entities
only from the matched result so order/size differences are handled safely.

Comment on lines +14 to +15
private final String timetableCategoryNotionId;
private final String timetableStageNotionId;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

데이터 모델 변경에 대한 마이그레이션을 검증하세요.

타임테이블 블록의 ID 필드가 long 타입에서 String 타입의 Notion ID로 변경되었습니다:

  • timetableCategoryIdtimetableCategoryNotionId
  • timetableAreaIdtimetableStageNotionId

이는 중요한 데이터 모델 변경으로, 다음 사항을 확인해야 합니다:

  1. 데이터베이스 스키마 마이그레이션이 준비되었는지
  2. 기존 데이터 처리 방안이 마련되었는지
  3. 모든 의존 코드가 새 필드명을 사용하도록 업데이트되었는지

다음 스크립트를 실행하여 마이그레이션 관련 파일과 이전 필드명의 잔여 사용을 확인하세요:


🏁 Script executed:

#!/bin/bash
# 데이터베이스 마이그레이션 파일 검색
echo "=== 마이그레이션 파일 검색 ==="
fd -e sql -e xml | head -20

# 이전 필드명 사용 확인
echo -e "\n=== timetableCategoryId 사용 검색 ==="
rg -n --type=java 'timetableCategoryId' -g '!TimetableBlock.java'

echo -e "\n=== timetableAreaId 사용 검색 ==="
rg -n --type=java 'timetableAreaId'

Length of output: 585


데이터 모델 변경 관련 마이그레이션 및 코드 반영 필요

  • 데이터베이스 마이그레이션(SQL/XML) 파일 미발견: DDL 또는 Liquibase/Flyway changelog 추가 필요
  • 기존 timetableCategoryId가 아직 사용 중 (
    – src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/TimetableCategory.java:10
    – src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/entity/TimetableCategoryEntity.java:17
    )
  • timetableAreaId 참조 위치는 없으나 timetableStageNotionId로 전역 변경 반영 필요

위 사항 해결을 위해 마이그레이션 스크립트 작성, 기존 데이터 변환 로직 구현, 필드명 일괄 교체를 수행하세요.

🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/block/core/domain/TimetableBlock.java
around lines 14-15, the model added timetableCategoryNotionId and
timetableStageNotionId but there is no DB migration or code changes to migrate
existing timetableCategoryId/timetableAreaId usage; create a database migration
(Liquibase/Flyway changelog or SQL) that adds the new columns, migrates data
from the old columns (populate timetableCategoryNotionId from
timetableCategoryId and timetableStageNotionId from timetableAreaId or
appropriate source), and drops or marks deprecated the old columns if desired;
update and refactor all Java references still using timetableCategoryId (e.g.,
TimetableCategory.java and TimetableCategoryEntity.java) to the new naming,
search-and-replace timetableAreaId -> timetableStageNotionId globally, add a
short data migration routine or script in the application startup if runtime
conversion is needed, and add tests and changelog entries to validate schema and
data migration.

Comment on lines +31 to +33
@Column(name = "notion_category_row_id", nullable = false)
private String notionCategoryRowId;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

새 컬럼 추가에 따른 마이그레이션/제약 보완 필요

  • 운영 DB에 NOT NULL 컬럼 추가는 기존 데이터가 있으면 실패합니다. 마이그레이션 스크립트(추가→백필→NOT NULL/UNIQUE 적용) 포함해 주세요.
  • Notion Row ID는 유일해야 할 가능성이 높습니다. unique + 길이 명시 및 인덱스를 권장합니다.

권장 변경 예:

-    @Column(name = "notion_category_row_id", nullable = false)
-    private String notionCategoryRowId;
+    @Column(name = "notion_category_row_id", nullable = false, unique = true, length = 64)
+    private String notionCategoryRowId;

DB 마이그레이션에서는 UNIQUE 인덱스도 함께 생성해 주세요.

🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/eventtimetable/category/core/domain/entity/TimetableCategoryEntity.java
around lines 31-33, the new notionCategoryRowId column is declared NOT NULL but
needs migration and constraint hardening: create a DB migration that 1) adds the
column as nullable, 2) backfills existing rows with a generated or lookup value
(or a placeholder) so no NULLs remain, 3) alters the column to NOT NULL and 4)
creates a UNIQUE index on the column; in the entity add length metadata (e.g.
appropriate varchar length) and mark uniqueness (either @Column(length=...,
unique=true) or add an @Index on the field) so the JPA mapping matches the DB,
and ensure the migration script also creates the UNIQUE index to enforce
uniqueness at the DB level.

Comment on lines +39 to +40
"/api/tickets/info/*",
"/api/tickets/confirm"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

티켓 엔드포인트의 공개 접근 허용에 대한 보안 검증이 필요합니다.

/api/tickets/info/*/api/tickets/confirm을 인증 없이 접근 가능하도록 변경하면 다음과 같은 보안 위험이 발생할 수 있습니다:

  • /api/tickets/info/*: 티켓 정보 조회 시 개인정보(구매자 정보, 결제 정보 등)가 노출될 가능성
  • /api/tickets/confirm: 인증 없이 티켓 확인/검증이 가능하면 무단 사용이나 티켓 상태 조작의 위험

다음 사항을 확인해주세요:

  1. 이 엔드포인트들이 노출하는 데이터에 민감 정보가 포함되어 있지 않은지
  2. /api/tickets/confirm이 티켓 상태를 변경하는 경우, 인증 없이 허용해도 안전한지
  3. Rate limiting이나 기타 보안 장치가 구현되어 있는지

다음 스크립트로 해당 엔드포인트의 구현을 확인하여 보안 위험을 검증하세요:


🏁 Script executed:

#!/bin/bash
# Description: 티켓 관련 컨트롤러와 서비스의 구현을 확인하여 데이터 노출 및 권한 검증 로직을 분석

# 티켓 컨트롤러에서 /info/* 및 /confirm 엔드포인트 구현 찾기
echo "=== 티켓 엔드포인트 구현 찾기 ==="
ast-grep --pattern $'@GetMapping($$$"/info/"$$$)
$$$
public $$$ $FUNC($$$) {
  $$$
}'

ast-grep --pattern $'@PostMapping($$$"/confirm"$$$)
$$$
public $$$ $FUNC($$$) {
  $$$
}'

# 티켓 정보 조회 및 확인 메서드에서 반환하는 DTO 확인
echo -e "\n=== 티켓 DTO 클래스 찾기 ==="
fd -e java -x rg -l "class.*Ticket.*Response|class.*Ticket.*Info|class.*Ticket.*Confirm" {} \; | head -10

# 티켓 서비스에서 개인정보 포함 여부 확인
echo -e "\n=== 티켓 서비스 메서드 확인 ==="
ast-grep --pattern $'class TicketService {
  $$$
  public $$$ getTicketInfo($$$) {
    $$$
  }
  $$$
}'

Length of output: 68


다음 스크립트를 실행하여 컨트롤러·DTO·서비스 구현을 재탐색해주세요:


🏁 Script executed:

#!/bin/bash
echo "=== TicketController 파일 위치 ==="
fd -e java -I TicketController.java

echo -e "\n=== /info 및 /confirm 매핑 검색 ==="
rg -nP --type java '@(GetMapping|PostMapping)\s*\(\s*"/api/tickets/(info|confirm)[^"]*"' -C3 src/main/java

echo -e "\n=== ticket.dto 패키지 내 DTO 클래스 ==="
rg -n 'package .*ticket\.dto' -l src/main/java

echo -e "\n=== DTO 클래스 목록 (Ticket 포함) ==="
fd -e java -g '*Ticket*.java' src/main/java/com/permitseoul/permitserver/**/dto | head -20

echo -e "\n=== TicketService 클래스 및 메서드 ==="
rg -nP --type java 'class\s+TicketService\b' -C3 src/main/java
rg -n 'TicketService' -C2 src/main/java

Length of output: 12298


다음 스크립트를 실행해 TicketController 내 매핑을 정확히 확인해주세요:


🏁 Script executed:

#!/bin/bash
FILE=src/main/java/com/permitseoul/permitserver/domain/ticket/api/controller/TicketController.java

echo "=== @GetMapping 어노테이션 ==="
rg -n '@GetMapping' -C2 $FILE

echo -e "\n=== @PostMapping 어노테이션 ==="
rg -n '@PostMapping' -C2 $FILE

echo -e "\n=== 'info' 키워드 검색 ==="
rg -n 'info' -C2 $FILE

echo -e "\n=== 'confirm' 키워드 검색 ==="
rg -n 'confirm' -C2 $FILE

Length of output: 1250


다음 스크립트로 confirmTicket 메서드 구현을 확인해주세요:


🏁 Script executed:

#!/bin/bash
rg -n 'confirmTicket' -C5 src/main/java/com/permitseoul/permitserver/domain/ticket/api/service/TicketService.java

Length of output: 541


다음 스크립트로 confirmTicket 메서드 내부의 상태 변경 로직을 확인해주세요:


🏁 Script executed:

#!/bin/bash
rg -nP --type java 'public void confirmTicket' -C20 src/main/java/com/permitseoul/permitserver/domain/ticket/api/service/TicketService.java

Length of output: 2215


/api/tickets/confirm 인증 및 권한 검사 적용 필요

  • confirmTicket 메서드가 ticketUpdater.updateTicketStatus로 상태 변경을 수행하므로 인증·인가 로직이 반드시 필요합니다.
  • /api/tickets/info/{eventId}가 노출하는 데이터에 민감 정보(구매자·결제 정보 등)가 없는지 확인하고, 필요 시 인증 처리 검토를 권장합니다.
🤖 Prompt for AI Agents
In src/main/java/com/permitseoul/permitserver/global/config/SecurityConfig.java
around lines 39-40, the endpoint "/api/tickets/confirm" is currently not
protected; add it to the list of routes requiring authentication/authorization
so that confirmTicket cannot be called anonymously and is subject to role checks
(e.g., requires authenticated user or specific role like ROLE_TICKET_UPDATER).
Also review "/api/tickets/info/*" for sensitive buyer/payment data and, if any
sensitive fields are returned, move that pattern from public to authenticated
(or add finer-grained authorization) so only authorized clients can access it.

Comment on lines +78 to 79
NOT_FOUND_TIMETABLE_STAGE(HttpStatus.NOT_FOUND, 40418, "타임테이블 장소를 찾을 수 없습니다."),
NOT_FOUND_TIMETABLE_CATEGORY(HttpStatus.NOT_FOUND, 40419, "타임테이블 카테고리를 찾을 수 없습니다."),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

메시지 용어 불일치(Area→Stage 전환 반영 필요)

상수명은 STAGE로 바뀌었지만, 메시지는 “장소”로 남아 있습니다. 사용자 혼동을 줄이기 위해 “무대”로 통일하세요.

-    NOT_FOUND_TIMETABLE_STAGE(HttpStatus.NOT_FOUND, 40418, "타임테이블 장소를 찾을 수 없습니다."),
+    NOT_FOUND_TIMETABLE_STAGE(HttpStatus.NOT_FOUND, 40418, "타임테이블 무대를 찾을 수 없습니다."),
📝 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
NOT_FOUND_TIMETABLE_STAGE(HttpStatus.NOT_FOUND, 40418, "타임테이블 장소를 찾을 수 없습니다."),
NOT_FOUND_TIMETABLE_CATEGORY(HttpStatus.NOT_FOUND, 40419, "타임테이블 카테고리를 찾을 수 없습니다."),
NOT_FOUND_TIMETABLE_STAGE(HttpStatus.NOT_FOUND, 40418, "타임테이블 무대를 찾을 수 없습니다."),
NOT_FOUND_TIMETABLE_CATEGORY(HttpStatus.NOT_FOUND, 40419, "타임테이블 카테고리를 찾을 수 없습니다."),
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java
around lines 78-79, the enum entries NOT_FOUND_TIMETABLE_STAGE and
NOT_FOUND_TIMETABLE_CATEGORY still use the Korean word "장소" ("place") in their
messages; update both message strings to use "무대" ("stage") to match the STAGE
naming and avoid user confusion. Keep the rest of the enum values untouched and
ensure the messages remain grammatically consistent with existing phrasing.

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

♻️ Duplicate comments (1)
src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java (1)

79-79: 메시지 용어 불일치 (Area→Stage 전환 반영 필요)

상수명은 NOT_FOUND_TIMETABLE_STAGE로 변경되었지만 메시지는 여전히 "타임테이블 장소를 찾을 수 없습니다."로 되어 있습니다. "무대"로 통일하는 것이 사용자 혼동을 줄일 수 있습니다.

다음 diff를 적용하여 메시지를 수정하세요:

-    NOT_FOUND_TIMETABLE_STAGE(HttpStatus.NOT_FOUND, 40418, "타임테이블 장소를 찾을 수 없습니다."),
+    NOT_FOUND_TIMETABLE_STAGE(HttpStatus.NOT_FOUND, 40418, "타임테이블 무대를 찾을 수 없습니다."),
🧹 Nitpick comments (2)
src/main/java/com/permitseoul/permitserver/global/external/notion/exception/NotFoundNotionResponseException.java (1)

5-6: 예외 클래스에 생성자 추가를 권장합니다.

예외 클래스가 메시지와 원인(cause)을 받을 수 있는 생성자를 제공하지 않고 있습니다. 디버깅과 로깅을 위해 컨텍스트 정보를 제공할 수 있는 생성자를 추가하는 것이 좋습니다.

다음 diff를 적용하여 표준 생성자를 추가하세요:

 public class NotFoundNotionResponseException extends PermitGlobalException {
+    public NotFoundNotionResponseException() {
+        super();
+    }
+
+    public NotFoundNotionResponseException(String message) {
+        super(message);
+    }
+
+    public NotFoundNotionResponseException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
src/main/java/com/permitseoul/permitserver/domain/admin/util/exception/PermitListSizeNotMatchException.java (1)

5-6: 예외 클래스에 생성자 추가를 권장합니다.

예외 클래스에 메시지와 원인을 전달할 수 있는 생성자가 없습니다. 어떤 리스트의 크기가 맞지 않는지 등의 컨텍스트 정보를 제공할 수 있도록 생성자를 추가하면 디버깅에 도움이 됩니다.

다음 diff를 적용하여 표준 생성자를 추가하세요:

 public class PermitListSizeNotMatchException extends AdminBaseException {
+    public PermitListSizeNotMatchException() {
+        super();
+    }
+
+    public PermitListSizeNotMatchException(String message) {
+        super(message);
+    }
+
+    public PermitListSizeNotMatchException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b943ca8 and f135b90.

📒 Files selected for processing (9)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableService.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/stage/core/AdminTimetableStageSaver.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/util/exception/PermitListSizeNotMatchException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/repository/TimetableStageRepository.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProvider.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/notion/exception/NotFoundNotionResponseException.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/permitseoul/permitserver/domain/admin/timetable/stage/core/AdminTimetableStageSaver.java
  • src/main/java/com/permitseoul/permitserver/global/external/notion/NotionProvider.java
🧰 Additional context used
🧬 Code graph analysis (5)
src/main/java/com/permitseoul/permitserver/global/external/notion/exception/NotFoundNotionResponseException.java (1)
src/main/java/com/permitseoul/permitserver/global/exception/PermitGlobalException.java (1)
  • PermitGlobalException (3-4)
src/main/java/com/permitseoul/permitserver/domain/admin/util/exception/PermitListSizeNotMatchException.java (1)
src/main/java/com/permitseoul/permitserver/domain/admin/base/AdminBaseException.java (1)
  • AdminBaseException (3-4)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableService.java (5)
src/main/java/com/permitseoul/permitserver/domain/admin/util/exception/PermitListSizeNotMatchException.java (1)
  • PermitListSizeNotMatchException (5-6)
src/main/java/com/permitseoul/permitserver/global/exception/DateFormatException.java (1)
  • DateFormatException (4-4)
src/main/java/com/permitseoul/permitserver/global/exception/PermitIllegalStateException.java (1)
  • PermitIllegalStateException (3-4)
src/main/java/com/permitseoul/permitserver/global/external/notion/NotionRelationValidator.java (1)
  • NotionRelationValidator (11-40)
src/main/java/com/permitseoul/permitserver/global/external/notion/exception/NotFoundNotionResponseException.java (1)
  • NotFoundNotionResponseException (5-6)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (5)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/stage/core/AdminTimetableStageSaver.java (1)
  • Component (10-18)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/block/core/component/AdminTimetableBlockSaver.java (1)
  • Component (11-21)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/core/components/AdminTimetableSaver.java (1)
  • Component (11-32)
src/main/java/com/permitseoul/permitserver/domain/admin/timetable/blockmedia/core/component/AdminTimetableBlockMediaSaver.java (1)
  • Component (11-19)
src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (1)
  • NotionResponseMapper (23-162)
src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (3)
src/main/java/com/permitseoul/permitserver/domain/admin/util/exception/PermitListSizeNotMatchException.java (1)
  • PermitListSizeNotMatchException (5-6)
src/main/java/com/permitseoul/permitserver/global/exception/DateFormatException.java (1)
  • DateFormatException (4-4)
src/main/java/com/permitseoul/permitserver/global/exception/PermitIllegalStateException.java (1)
  • PermitIllegalStateException (3-4)
🔇 Additional comments (10)
src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java (1)

31-31: 새로운 에러 코드가 적절하게 추가되었습니다.

Notion 통합 기능에 필요한 에러 코드들(BAD_REQUEST_MISMATCH_LIST_SIZE, NOT_FOUND_NOTION_RELATION_ID, NOT_FOUND_NOTION_DATABASE_SOURCE, INTERNAL_NOTION_FEIGN_ERROR)이 잘 정의되었습니다.

Also applies to: 87-88, 128-128

src/main/java/com/permitseoul/permitserver/domain/eventtimetable/stage/core/repository/TimetableStageRepository.java (1)

10-15: 리포지토리 구현이 적절합니다.

JPA 리포지토리가 올바르게 정의되었으며, findAllByTimetableIdfindByNotionStageRowId 메서드는 Spring Data JPA의 네이밍 규칙을 따르고 있습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableFacade.java (1)

39-71: 트랜잭션 관리 및 순차적 저장 흐름이 적절합니다.

@Transactional 어노테이션을 사용하여 전체 저장 프로세스의 원자성을 보장하고 있으며, timetable → stages → categories → blocks → block media 순서로 의존성을 고려한 순차적 저장이 올바르게 구현되었습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/timetable/base/api/service/AdminTimetableService.java (2)

37-47: Notion 데이터 소스 조회 및 검증 로직이 적절합니다.

각 데이터 소스 ID가 올바른 메서드로 전달되고 있으며, NotionRelationValidator를 통한 연관 관계 검증도 적절히 수행되고 있습니다.


62-72: 예외 처리 매핑이 잘 구조화되어 있습니다.

각 예외 유형을 적절한 에러 코드와 매핑하여 AdminApiException으로 변환하고 있습니다. Feign 통신 오류, 관계 ID 누락, 날짜 형식 오류, 리스트 크기 불일치 등 다양한 시나리오를 처리하고 있습니다.

src/main/java/com/permitseoul/permitserver/domain/admin/util/NotionResponseMapper.java (5)

26-48: Stage 엔티티 매핑 로직이 적절합니다.

Notion 응답에서 stage name, sequence 추출 시 findFirst()orElse()를 사용하여 null-safe하게 처리하고 있으며, sequence가 null인 경우 -1을 기본값으로 설정하는 것도 적절합니다.


50-87: Category 엔티티 매핑 로직이 적절합니다.

Category name, background color, line color를 Notion 응답에서 안전하게 추출하며, 빈 값에 대한 적절한 기본값 처리가 구현되어 있습니다.


106-111: 날짜 파싱 예외 처리가 적절하게 추가되었습니다.

LocalDateTime.parse()에서 발생할 수 있는 DateTimeParseException을 catch하여 DateFormatException으로 변환하고 있어, 잘못된 날짜 형식에 대한 적절한 오류 처리가 구현되었습니다.


113-118: Relation 검증 로직이 적절하게 추가되었습니다.

relation().isEmpty() 체크를 통해 get(0) 호출 전 안전성을 확보하고 있으며, 누락된 relation에 대해 PermitIllegalStateException을 발생시켜 적절히 처리하고 있습니다.


138-161: saveAll()가 입력 순서를 유지합니다
Spring Data JPA의 saveAll()는 전달된 Iterable 순서를 그대로 반환하므로, savedBlocks와 Notion 결과 간 인덱스 기반 매핑은 안전합니다.

@sjk4618 sjk4618 merged commit 3f3b1e6 into dev Oct 16, 2025
2 checks passed
@sjk4618 sjk4618 deleted the feat/#157 branch October 16, 2025 05:26
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