Skip to content

✨ [FEAT] 행사 관리 및 플랫폼 심사·광고 기능 구현 - #14

Merged
dolele092-source merged 7 commits into
devfrom
feature/event-management
Aug 4, 2026
Merged

dolele092-source merged 7 commits into
devfrom
feature/event-management

Conversation

@dolele092-source

@dolele092-source dolele092-source commented Aug 4, 2026 •

Copy link
Copy Markdown
Collaborator

📄 작업 내용

행사 생성부터 플랫폼 심사, 승인 후 공개까지의 전체 흐름을 구현하고 행사·부스 광고 관리 기능을 연결했습니다.

  • 행사 생성·수정·목록·상세 조회 및 상태 전환 API 구현
  • 행사 담당자 관리 기능 구현
  • 플랫폼 관리자 행사 승인·반려 기능 구현
  • 개최자 승인 요청 및 APPROVED → PUBLISHED 공개 흐름 구현
  • 행사·부스 광고 신청, 승인·반려 및 노출 상태 관리 구현
  • Kakao 장소 검색, 우편번호 자동 입력 및 지도 미리보기 연결
  • 개최자·플랫폼 관리자 화면을 실제 API 데이터와 연결
  • 부스 모집 사용 시 실제 모집 완료 상태를 승인 요청 조건으로 연동
  • 로컬 Vite API 프록시와 환경변수 예시 정리

🔗 관련 이슈

없음

☑️ 체크리스트

  • 로컬에서 정상 동작 확인
  • 테스트 작성 및 통과
  • 불필요한 로그/주석/커밋 정리

📸 스크린샷 (UI 변경 시)

  • 행사 등록, 개최자 승인 요청, 플랫폼 관리자 심사 화면 변경
  • 필요 시 리뷰 코멘트에 추가하겠습니다.

💬 리뷰어에게

  • 백엔드 전체 테스트 101개 통과했습니다.
  • 프론트엔드 프로덕션 빌드가 통과했습니다.
  • 최신 origin/dev를 rebase하여 반영했습니다.
  • Windows의 한글 프로젝트 경로에서 Gradle 9 테스트 런처 문제가 있어 subst로 연결한 ASCII 경로에서 전체 테스트를 검증했습니다.
  • .env의 실제 OAuth, Kakao, DB 키는 커밋에 포함하지 않았습니다.

Summary by CodeRabbit

  • 새 기능
    • 행사 생성·수정, 승인·반려·게시·취소 및 담당자 관리를 지원합니다.
    • 행사 상세 조회와 검색·유형 필터를 제공합니다.
    • 장소 검색, 우편번호 조회, 카카오 지도 미리보기를 지원합니다.
    • 광고 생성·수정·취소 및 관리자 승인·반려를 제공합니다.
    • 행사·광고 상태가 일정에 따라 자동 갱신됩니다.
    • 관리자 화면에서 행사·광고를 조회하고 처리할 수 있습니다.
    • 존재하지 않는 페이지 안내와 홈 이동을 제공합니다.
  • 개선 사항
    • API 연동으로 최신 행사·광고 정보를 표시합니다.
    • 로딩, 오류 및 검색 결과 없음 상태 안내를 강화했습니다.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

이벤트·광고 관리 백엔드와 프론트엔드 연동을 추가했습니다. 이벤트 상태 전환, 멤버 관리, 장소 검색, Kakao Map, 광고 승인 흐름, 관리자 화면, API 기반 홈 화면을 구현했습니다.

Changes

이벤트 및 장소 데이터 계약

Layer / File(s) Summary
이벤트 및 장소 데이터 계약
BE/src/main/java/com/min/edu/event/domain/*, BE/src/main/java/com/min/edu/event/dto/*, BE/src/main/java/com/min/edu/event/service/LocationService.java, BE/src/main/resources/db/migration/*, FE/src/pages/EventForm.jsx, FE/src/components/KakaoMapPreview.jsx
이벤트 주소·좌표 필드, DTO, 장소 검색 및 우편번호 조회 API, Kakao Map 미리보기를 추가했습니다.

이벤트 상태 및 관리 API

Layer / File(s) Summary
이벤트 상태 및 관리 API
BE/src/main/java/com/min/edu/event/controller/*, BE/src/main/java/com/min/edu/event/service/*, BE/src/main/java/com/min/edu/event/repository/*, FE/src/api/eventApi.js, FE/src/pages/EventMembers.jsx
이벤트 조회·생성·수정·승인·게시·취소와 멤버 관리를 추가했습니다. 권한 검증과 만료 상태 갱신을 구현했습니다.

광고 상태 및 관리 API

Layer / File(s) Summary
광고 상태 및 관리 API
BE/src/main/java/com/min/edu/advertisement/*, FE/src/api/advertisementApi.js
광고 생성·조회·수정·취소·승인·거절과 부스 후보 조회를 추가했습니다. 광고 상태 전환과 권한 검증을 구현했습니다.

프론트엔드 API 및 관리 화면

Layer / File(s) Summary
프론트엔드 API 및 관리 화면
FE/src/pages/*, FE/src/main.jsx, FE/src/api/locationApi.js, FE/vite.config.js, .env.example
이벤트·광고·장소 API, /api 프록시, 이벤트 관리 화면, 홈과 플랫폼 관리자 화면, 404 라우트를 연결했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Organizer
  participant EventForm
  participant LocationService
  participant KakaoMapPreview
  participant EventService
  Organizer->>EventForm: 행사 정보 입력
  EventForm->>LocationService: 장소 및 우편번호 검색
  LocationService-->>EventForm: 장소, 주소, 좌표 반환
  EventForm->>KakaoMapPreview: 선택 좌표 전달
  KakaoMapPreview-->>Organizer: 지도 및 마커 표시
  EventForm->>EventService: 이벤트 생성 또는 수정 요청
  EventService-->>EventForm: 저장 결과 반환
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 행사 관리, 플랫폼 심사, 광고 기능 구현이라는 주요 변경 내용을 명확하게 요약하며 저장소의 제목 형식도 따릅니다.
Description check ✅ Passed 작업 내용, 관련 이슈, 체크리스트, UI 변경 사항, 리뷰어 안내를 포함하고 주요 구현 및 검증 결과를 구체적으로 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/event-management

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

@dolele092-source dolele092-source changed the title feat: 행사 관리 및 플랫폼 심사·광고 기능 구현 ✨ [FEAT] 행사 관리 및 플랫폼 심사·광고 기능 구현 Aug 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 18

🧹 Nitpick comments (3)
BE/src/main/java/com/min/edu/event/service/EventService.java (2)

267-273: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

전이 실패 원인이 응답에서 사라집니다.

Event.submit, Event.publish 같은 도메인 메서드는 "공개 또는 중단 상태의 행사만 종료할 수 있습니다." 같은 구체적인 메시지를 담아 예외를 던집니다. 지금은 그 메시지를 버리고 INVALID_INPUT_VALUE만 남깁니다. 사용자는 왜 실패했는지 알 수 없고, 서버 로그에도 흔적이 없어 디버깅이 어렵습니다. 최소한 원인 예외는 연결해주세요.

♻️ 제안 수정
     private void transition(Runnable action) {
         try {
             action.run();
         } catch (IllegalStateException exception) {
-            throw new BusinessException(GlobalErrorCode.INVALID_INPUT_VALUE);
+            throw new BusinessException(GlobalErrorCode.INVALID_INPUT_VALUE, exception.getMessage());
         }
     }

BusinessException에 메시지를 받는 생성자가 없으면, 우선 log.warn으로 원인 메시지라도 남겨주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BE/src/main/java/com/min/edu/event/service/EventService.java` around lines
267 - 273, Update EventService.transition to preserve the caught
IllegalStateException as the cause when throwing BusinessException with
GlobalErrorCode.INVALID_INPUT_VALUE, and retain the original exception message
for the response if BusinessException supports a message constructor. If that
constructor is unavailable, log the cause message with the service’s existing
warning logger before rethrowing.

69-71: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

like 패턴의 와일드카드를 이스케이프하는 편이 좋습니다.

keyword를 그대로 %...%에 넣습니다. 파라미터 바인딩이라 SQL 인젝션은 아니지만, 사용자가 %나 _를 입력하면 전체 매칭에 가까운 결과가 나옵니다. 또 앞쪽 와일드카드 때문에 name 인덱스를 타지 못합니다. 데이터가 늘어나면 목록 조회가 느려집니다.

♻️ 제안 수정
-            if (keyword != null && !keyword.isBlank()) {
-                predicates.add(cb.like(cb.lower(root.get("name")), "%" + keyword.toLowerCase() + "%"));
-            }
+            if (keyword != null && !keyword.isBlank()) {
+                String escaped = keyword.toLowerCase()
+                        .replace("!", "!!").replace("%", "!%").replace("_", "!_");
+                predicates.add(cb.like(cb.lower(root.get("name")), "%" + escaped + "%", '!'));
+            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BE/src/main/java/com/min/edu/event/service/EventService.java` around lines 69
- 71, Update the keyword predicate in EventService’s query construction to
escape LIKE wildcard characters such as “%” and “_” before building the contains
pattern, and apply the corresponding escape character to the CriteriaBuilder
like expression. Preserve case-insensitive matching while preventing
user-entered wildcards from broadening results; do not change the existing
contains semantics unless the surrounding search contract requires it.
BE/src/main/java/com/min/edu/advertisement/dto/AdvertisementDtos.java (1)

22-31: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

reviewedBy를 신청자에게도 내려줄지 결정해주세요.

AdvertisementService.get은 신청 조직의 멤버에게도 이 Response를 그대로 반환합니다. reviewedBy는 심사한 플랫폼 관리자의 내부 memberId입니다. 신청자에게 심사자 식별자까지 노출할 필요는 없어 보입니다. 관리자용 응답과 신청자용 응답을 나누거나, 신청자 응답에서는 reviewedBy를 비워주세요.

경로 지시사항의 "민감정보가 코드, 로그, 응답에 노출되는 문제" 항목을 근거로 제안합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BE/src/main/java/com/min/edu/advertisement/dto/AdvertisementDtos.java` around
lines 22 - 31, AdvertisementDtos.Response와 AdvertisementService.get 흐름을 조정해 신청
조직 멤버에게 반환되는 응답에서는 내부 플랫폼 관리자 식별자인 reviewedBy를 null로 비워주세요. 관리자용 응답에서는 기존
reviewedBy 값을 유지하거나, 필요하면 관리자용과 신청자용 응답 생성을 분리해 권한에 맞게 반환하세요.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@BE/src/main/java/com/min/edu/advertisement/domain/Advertisement.java`:
- Around line 88-96: Advertisement.approve must reject expired advertisements
before changing status; validate that endAt is strictly after now and throw an
appropriate exception when it is not, while preserving existing approval
behavior for valid ads. Add a regression test in AdvertisementTest covering an
approve call at or after endAt and asserting that it throws.

In
`@BE/src/main/java/com/min/edu/advertisement/service/AdvertisementService.java`:
- Around line 51-58: Update findActive to apply the optional eventId filter
through the advertisementRepository query instead of filtering the full result
in the stream, while preserving the existing active and scheduled time
conditions. Remove the activeComparator method and replace its event lookups
with a single cached sorting-key retrieval per advertisement before sorting,
avoiding repeated eventRepository.findById calls during comparison.

In `@BE/src/main/java/com/min/edu/event/dto/EventDtos.java`:
- Around line 29-31: EventDtos의 latitude와 longitude 필드에 위도·경도 유효 범위를 검증하는
Jakarta Bean Validation 제약을 추가하세요. DecimalMax와 필요한 DecimalMin을 임포트하고, latitude는
-90~90, longitude는 -180~180 범위만 허용하도록 설정해 컨트롤러 진입 시 잘못된 값을 거부하세요.

In `@BE/src/main/java/com/min/edu/event/repository/EventRepository.java`:
- Around line 14-15: 마이그레이션에 상태 및 시간 조건 조회용 복합 인덱스를 추가하세요: events(status,
end_at), advertisements(status, start_at), advertisements(status, end_at).
EventRepository의 findAllByStatusInAndEndAtLessThanEqual 및
AdvertisementRepository의 관련 상태·시각 조회가 해당 인덱스를 사용하도록 기존 events(status,
start_at)만으로 대체하지 마세요. 변경 대상은
BE/src/main/java/com/min/edu/event/repository/EventRepository.java 14-15행과
BE/src/main/java/com/min/edu/advertisement/repository/AdvertisementRepository.java
15-18행이며, 실제 인덱스 정의는 프로젝트의 마이그레이션 파일에 반영하세요.

In `@BE/src/main/java/com/min/edu/event/service/EventLifecycleScheduler.java`:
- Around line 24-37: Update EventLifecycleScheduler.updateLifecycleStatuses to
isolate failures per event and advertisement, catching exceptions around
event.end(now), ad.activate(now), and ad.end(now) so one invalid record does not
roll back or block the remaining updates. Add distributed scheduling protection
such as ShedLock, or use pessimistic-locking repository queries, to prevent
multiple scheduler instances from processing the same rows concurrently.

In `@BE/src/main/java/com/min/edu/event/service/EventService.java`:
- Around line 127-143: Update EventService.update to enforce both editability
and inventory constraints before calling Event.update: allow updates only for
the domain-approved mutable event statuses, and reject any request whose
ticketTotalQuantity is below the event’s existing ticketSoldQuantity. Extend
validation beyond validateRequest as needed while preserving the current request
checks and normal update flow.

In `@BE/src/main/java/com/min/edu/event/service/LocationService.java`:
- Around line 13-17: Update the RestClient construction in LocationService to
use a requestFactory with connection and response timeouts, matching the
existing NtsBusinessApiClient configuration. In the Kakao API call flow using
retrieve(), catch or map client/server response exceptions and communication
failures to the project’s external-integration error type so
GlobalExceptionHandler returns the intended external integration response
instead of a generic 500.

In `@FE/src/components/KakaoMapPreview.jsx`:
- Around line 10-16: Update the SDK-loading promise initialization so every
failure clears the cached sdkPromise back to undefined before rejecting,
including script.onerror and the case where window.kakao?.maps is unavailable.
Preserve successful loading and resolution through window.kakao.maps.load, while
ensuring subsequent component openings can retry.

In `@FE/src/pages/EventDetail.jsx`:
- Around line 18-24: Update the event-detail useEffect keyed by eventId to call
setError("") when a new request starts and track whether the effect is still
active with a cleanup flag. Guard the setEvent, setError, and setLoading calls
in the then, catch, and finally handlers so responses, errors, and loading
completion from prior eventApi.detail requests cannot affect the current event.

In `@FE/src/pages/EventForm.jsx`:
- Around line 84-95: Update selectPlace to track each place selection with a
useRef sequence, incrementing it before the postalCode request. Apply the
response postal code or setPlaceError only when the request’s sequence still
matches the latest selection, preventing stale responses from overwriting the
current venue.
- Around line 39-70: 수정 화면의 상세 로딩 상태가 모든 경로에서 종료되도록 업데이트하세요. 조직 조회 Effect에서
eventId가 있을 때 조직 조회 실패 시 setLoading(false)를 호출하고, organizationId가 없어 상세 조회
Effect가 조기 반환하는 경우에도 setLoading(false)를 호출하세요. eventId와 organizationId가 변경되어
managedDetail 조회를 시작할 때는 먼저 setLoading(true)를 호출하며, 기존 managedDetail 성공·실패 경로의
종료 처리는 유지하세요.
- Around line 18-19: Update toInputDateTime in EventForm.jsx to use the existing
toDatetimeLocal utility from datetime.js, or equivalent local-time formatting,
instead of slicing toISOString() UTC output. Preserve the empty-string behavior
for falsy values and keep toOffsetDateTime unchanged.

In `@FE/src/pages/EventMembers.jsx`:
- Around line 21-29: Update the toggle and remove handlers to track per-member
in-progress state, prevent duplicate actions by disabling that member’s
toggle/remove controls while its request is pending, and wrap each request in
try/catch. Call setError with the request message or existing fallback on
failure, and call load only after a successful update or removal.

In `@FE/src/pages/Home.jsx`:
- Around line 124-129: Update the independent requests in FE/src/pages/Home.jsx
lines 124-129 so event results update via setEvents even when
advertisementApi.active fails, and advertisements update independently via
setActiveAds; use Promise.allSettled or separate try/catch handling. Apply the
same independent-update behavior in FE/src/pages/PlatformAdmin.jsx lines 57-84,
showing an error only for the event or advertisement data type whose request
failed.
- Around line 99-107: 동적 광고로 displayedSlides 길이가 변경될 때 heroIdx가 유효한 범위를 유지하도록
정규화하세요. displayedSlides와 heroIdx를 사용하는 Home 컴포넌트의 슬라이드 상태 또는 효과 로직에서 현재 인덱스를 새
slideCount에 맞춰 조정하고, 빈 배열일 때도 안전하게 처리하세요. 기존 translateX 및 정상 범위의 슬라이드 동작은 유지하세요.

In `@FE/src/pages/OrganizerAdmin.jsx`:
- Around line 353-358: Connect the floor-plan and booth-coordinate checklist
item near the recruitment status display to a server-provided validated
completion state instead of always rendering “완료.” Use that state when
calculating whether the event is ready for approval/request, and show the item
as pending or incomplete when the approval condition is not satisfied; do not
mark it complete solely because the event exists.

In `@FE/src/pages/PlatformAdmin.jsx`:
- Around line 68-72: Update the status mapping in the event transformation so
only explicitly recognized pre-submission statuses become inactive. Preserve
pending, approved, and rejected mappings, but return the original event.status
for unknown or additional values such as CANCELLED and ENDED, allowing the
rawStatus fallback near eventStatusLabel to display them.
- Around line 115-123: Update decideAd to wrap the advertisementApi.approve or
advertisementApi.reject request and the subsequent loadAdminData call in
try/catch, and call setLoadError with the caught error when any operation fails.
Preserve the existing prompt cancellation behavior and successful reload flow.

---

Nitpick comments:
In `@BE/src/main/java/com/min/edu/advertisement/dto/AdvertisementDtos.java`:
- Around line 22-31: AdvertisementDtos.Response와 AdvertisementService.get 흐름을
조정해 신청 조직 멤버에게 반환되는 응답에서는 내부 플랫폼 관리자 식별자인 reviewedBy를 null로 비워주세요. 관리자용 응답에서는 기존
reviewedBy 값을 유지하거나, 필요하면 관리자용과 신청자용 응답 생성을 분리해 권한에 맞게 반환하세요.

In `@BE/src/main/java/com/min/edu/event/service/EventService.java`:
- Around line 267-273: Update EventService.transition to preserve the caught
IllegalStateException as the cause when throwing BusinessException with
GlobalErrorCode.INVALID_INPUT_VALUE, and retain the original exception message
for the response if BusinessException supports a message constructor. If that
constructor is unavailable, log the cause message with the service’s existing
warning logger before rethrowing.
- Around line 69-71: Update the keyword predicate in EventService’s query
construction to escape LIKE wildcard characters such as “%” and “_” before
building the contains pattern, and apply the corresponding escape character to
the CriteriaBuilder like expression. Preserve case-insensitive matching while
preventing user-entered wildcards from broadening results; do not change the
existing contains semantics unless the surrounding search contract requires it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 62394123-f65c-4f44-a1bd-8a7cebd0b135

📥 Commits

Reviewing files that changed from the base of the PR and between cbf0368 and 249cb6b.

📒 Files selected for processing (40)
  • .env.example
  • BE/src/main/java/com/min/edu/advertisement/controller/AdvertisementController.java
  • BE/src/main/java/com/min/edu/advertisement/domain/Advertisement.java
  • BE/src/main/java/com/min/edu/advertisement/dto/AdvertisementDtos.java
  • BE/src/main/java/com/min/edu/advertisement/repository/AdvertisementBoothRepository.java
  • BE/src/main/java/com/min/edu/advertisement/repository/AdvertisementRepository.java
  • BE/src/main/java/com/min/edu/advertisement/service/AdvertisementService.java
  • BE/src/main/java/com/min/edu/event/config/EventSchedulingConfig.java
  • BE/src/main/java/com/min/edu/event/controller/EventController.java
  • BE/src/main/java/com/min/edu/event/controller/LocationController.java
  • BE/src/main/java/com/min/edu/event/domain/Event.java
  • BE/src/main/java/com/min/edu/event/domain/EventMember.java
  • BE/src/main/java/com/min/edu/event/dto/EventDtos.java
  • BE/src/main/java/com/min/edu/event/dto/LocationDtos.java
  • BE/src/main/java/com/min/edu/event/repository/EventBoothRecruitmentRepository.java
  • BE/src/main/java/com/min/edu/event/repository/EventMemberRepository.java
  • BE/src/main/java/com/min/edu/event/repository/EventOrganizationMemberRepository.java
  • BE/src/main/java/com/min/edu/event/repository/EventOrganizationRepository.java
  • BE/src/main/java/com/min/edu/event/repository/EventRepository.java
  • BE/src/main/java/com/min/edu/event/service/EventLifecycleScheduler.java
  • BE/src/main/java/com/min/edu/event/service/EventService.java
  • BE/src/main/java/com/min/edu/event/service/LocationService.java
  • BE/src/main/resources/application.properties
  • BE/src/main/resources/db/migration/V6__add_event_location_fields.sql
  • BE/src/test/java/com/min/edu/advertisement/domain/AdvertisementTest.java
  • BE/src/test/java/com/min/edu/event/domain/EventTest.java
  • BE/src/test/resources/application.properties
  • FE/src/api/advertisementApi.js
  • FE/src/api/eventApi.js
  • FE/src/api/locationApi.js
  • FE/src/components/KakaoMapPreview.jsx
  • FE/src/main.jsx
  • FE/src/pages/EventDetail.jsx
  • FE/src/pages/EventForm.jsx
  • FE/src/pages/EventMembers.jsx
  • FE/src/pages/Home.jsx
  • FE/src/pages/NotFound.jsx
  • FE/src/pages/OrganizerAdmin.jsx
  • FE/src/pages/PlatformAdmin.jsx
  • FE/vite.config.js

Comment thread BE/src/main/java/com/min/edu/event/dto/EventDtos.java Outdated
Comment thread BE/src/main/java/com/min/edu/event/repository/EventRepository.java Outdated
Comment on lines +24 to +37
@Scheduled(cron = "${app.lifecycle.cron:0 * * * * *}")
@Transactional
public void updateLifecycleStatuses() {
OffsetDateTime now = OffsetDateTime.now();
eventRepository.findAllByStatusInAndEndAtLessThanEqual(
List.of(EventStatus.PUBLISHED, EventStatus.SUSPENDED), now)
.forEach(event -> event.end(now));
advertisementRepository.findAllByStatusAndStartAtLessThanEqual(
AdvertisementStatus.SCHEDULED, now).stream()
.filter(ad -> ad.getEndAt().isAfter(now)).forEach(ad -> ad.activate(now));
advertisementRepository.findAllByStatusInAndEndAtLessThanEqual(
List.of(AdvertisementStatus.SCHEDULED, AdvertisementStatus.ACTIVE), now)
.forEach(ad -> ad.end(now));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

한 건이 실패하면 배치 전체가 롤백됩니다.

event.end(now)와 ad.activate(now)는 상태 조건이 맞지 않으면 IllegalStateException을 던집니다. 이 메서드는 그 예외를 잡지 않습니다. 대상 100건 중 1건만 실패해도 @Transactional이 전부 롤백하고, 나머지 99건은 종료되지 않습니다. 조건이 그대로면 다음 주기에도 같은 건에서 다시 실패합니다. 즉 라이프사이클 갱신이 영구적으로 멈출 수 있습니다.

여기에 인스턴스를 2대 이상 띄우면 두 스케줄러가 같은 행을 동시에 잡습니다. 락이 없어 한쪽은 예외 또는 갱신 충돌을 만납니다.

건별로 예외를 격리하고, 다중 인스턴스 환경이면 ShedLock 같은 분산 락 또는 비관적 락 조회를 붙여주세요.

🛡️ 제안 수정(예외 격리)
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
...
+    private static final Logger log = LoggerFactory.getLogger(EventLifecycleScheduler.class);
+
     `@Scheduled`(cron = "${app.lifecycle.cron:0 * * * * *}")
     `@Transactional`
     public void updateLifecycleStatuses() {
         OffsetDateTime now = OffsetDateTime.now();
         eventRepository.findAllByStatusInAndEndAtLessThanEqual(
                 List.of(EventStatus.PUBLISHED, EventStatus.SUSPENDED), now)
-                .forEach(event -> event.end(now));
+                .forEach(event -> safely(() -> event.end(now), "event", event.getId()));
...
+    private void safely(Runnable action, String type, Long id) {
+        try {
+            action.run();
+        } catch (IllegalStateException exception) {
+            log.warn("lifecycle transition skipped. type={}, id={}, reason={}", type, id, exception.getMessage());
+        }
+    }

광고 처리 두 곳에도 같은 방식으로 감싸주세요.

경로 지시사항의 "예외 처리 누락", "중복 요청과 동시성 문제" 항목을 근거로 제안합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BE/src/main/java/com/min/edu/event/service/EventLifecycleScheduler.java`
around lines 24 - 37, Update EventLifecycleScheduler.updateLifecycleStatuses to
isolate failures per event and advertisement, catching exceptions around
event.end(now), ad.activate(now), and ad.end(now) so one invalid record does not
roll back or block the remaining updates. Add distributed scheduling protection
such as ShedLock, or use pessimistic-locking repository queries, to prevent
multiple scheduler instances from processing the same rows concurrently.

Source: Path instructions

Comment thread FE/src/pages/Home.jsx
Comment thread FE/src/pages/Home.jsx Outdated
Comment on lines +124 to +129
const [eventResult, adResult] = await Promise.all([
eventApi.list({ size: 6, sort: "startAt,asc", ...filters }),
advertisementApi.active(),
]);
setEvents(eventResult?.data?.content || []);
setActiveAds(adResult?.data || []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

독립 API 요청을 하나의 실패로 묶지 마세요.

Promise.all은 요청 하나가 실패하면 성공한 다른 응답도 상태에 반영하지 않습니다. 광고 API 장애가 발생하면 홈의 행사 목록과 관리자 행사 승인 목록도 표시되지 않습니다.

  • FE/src/pages/Home.jsx#L124-L129: 행사 조회 성공 시 광고 조회 실패와 무관하게 행사 목록을 유지하세요.
  • FE/src/pages/PlatformAdmin.jsx#L57-L84: 행사 목록과 광고 목록을 각각 갱신하고, 실패한 데이터 종류만 오류로 표시하세요.

Promise.allSettled 또는 개별 try/catch를 사용하세요.

As per path instructions, "예외 처리 누락"과 "성능 저하 가능성과 불필요한 쿼리 또는 연산"을 중점적으로 확인했습니다.

📍 Affects 2 files
  • FE/src/pages/Home.jsx#L124-L129 (this comment)
  • FE/src/pages/PlatformAdmin.jsx#L57-L84
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/Home.jsx` around lines 124 - 129, Update the independent
requests in FE/src/pages/Home.jsx lines 124-129 so event results update via
setEvents even when advertisementApi.active fails, and advertisements update
independently via setActiveAds; use Promise.allSettled or separate try/catch
handling. Apply the same independent-update behavior in
FE/src/pages/PlatformAdmin.jsx lines 57-84, showing an error only for the event
or advertisement data type whose request failed.

Source: Path instructions

Comment thread FE/src/pages/OrganizerAdmin.jsx
Comment thread FE/src/pages/PlatformAdmin.jsx Outdated
Comment on lines +68 to +72
status: ["SUBMITTED", "UNDER_REVIEW"].includes(event.status) ? "pending"
: ["APPROVED", "PUBLISHED"].includes(event.status) ? "approved"
: event.status === "REJECTED" ? "rejected" : "inactive",
rawStatus: event.status,
reason: event.rejectionReason,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

알 수 없는 행사 상태를 inactive로 변환하지 마세요.

현재 status는 항상 eventStatusLabel의 키로 변환됩니다. 따라서 Line 237의 r.rawStatus 대체 표시는 실행되지 않습니다. CANCELLED, ENDED 같은 추가 상태도 모두 "제출 전"으로 표시됩니다.

알려진 미제출 상태만 inactive로 변환하고, 나머지 상태는 원시 값을 유지하세요.

As per path instructions, "비즈니스 로직 오류"를 중점적으로 확인했습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/PlatformAdmin.jsx` around lines 68 - 72, Update the status
mapping in the event transformation so only explicitly recognized pre-submission
statuses become inactive. Preserve pending, approved, and rejected mappings, but
return the original event.status for unknown or additional values such as
CANCELLED and ENDED, allowing the rawStatus fallback near eventStatusLabel to
display them.

Source: Path instructions

Comment thread FE/src/pages/PlatformAdmin.jsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
FE/src/pages/OrganizerAdmin.jsx (1)

354-360: 🎯 Functional Correctness | 🟠 Major

평면도 사용 여부를 완료 상태로 사용하지 말고 승인 조건과 일치시키세요.

selectedEvent?.venueMapEnabled는 기능 사용 여부입니다. 평면도 업로드와 부스 좌표 등록 완료 여부가 아닙니다. 현재 행은 실제 등록 상태를 확인하지 못하고, 활성화된 행사에서 항상 평면도 파트 연동 필요로 표시됩니다.

또한 approvalRequestReady는 Line 126에서 이 상태를 검사하지 않습니다. 다른 조건이 충족되면 평면도와 좌표가 미등록이어도 승인 요청 버튼이 활성화될 수 있습니다. 서버가 검증한 완료 상태를 사용하고, 평면도가 승인 조건이면 동일한 상태를 승인 요청 조건에도 포함하세요. 행사 정보가 로드되지 않은 동안에는 selectedEvent가 없다는 이유로 해당 없음과 완료 아이콘을 표시하지 말고 로딩 상태를 분리하세요.

As per path instructions: 비즈니스 로직 오류와 승인 조건 불일치를 우선 확인했습니다.

다음 검색으로 실제 완료 필드와 서버 승인 조건의 계약을 확인해 주세요.

검증 스크립트
#!/bin/bash
set -euo pipefail

# 평면도 완료 상태와 승인 요청 조건의 계약을 추적합니다.
rg -n -C 6 \
  'venueMapEnabled|floorplan|floorPlan|부스.*좌표|approvalRequestReady|/submission|submit\(' \
  FE/src BE/src
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/OrganizerAdmin.jsx` around lines 354 - 360,
selectedEvent.venueMapEnabled를 평면도·부스 좌표 등록 완료 상태로 사용하지 말고, 서버가 제공하는 실제 완료 필드를
사용하도록 해당 행의 아이콘과 문구를 수정하세요. 행사 정보가 로드되기 전에는 완료/해당 없음 대신 기존 로딩 상태를 표시하세요.
approvalRequestReady 계산에서도 동일한 서버 검증 완료 조건을 포함해 평면도 승인 조건과 화면 상태가 일치하도록 업데이트하세요.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@BE/src/main/java/com/min/edu/advertisement/repository/AdvertisementRepository.java`:
- Around line 16-20: Update both AdvertisementRepository methods
findAllByStatusInAndStartAtLessThanEqualAndEndAtGreaterThanEqual and
findAllByEventIdAndStatusInAndStartAtLessThanEqualAndEndAtGreaterThanEqual to
use the exclusive EndAtGreaterThan predicate, including renaming the derived
method suffixes while preserving the other conditions and parameters.

In `@BE/src/main/resources/db/migration/V7__add_lifecycle_query_indexes.sql`:
- Around line 1-8: 마이그레이션의 세 인덱스 생성문을 모두 CREATE INDEX CONCURRENTLY로 변경하고, 해당
마이그레이션이 트랜잭션 없이 실행되도록 비트랜잭션 마이그레이션 설정을 추가하세요. 기존 IF NOT EXISTS 동작과 인덱스 이름은
유지하세요.

---

Duplicate comments:
In `@FE/src/pages/OrganizerAdmin.jsx`:
- Around line 354-360: selectedEvent.venueMapEnabled를 평면도·부스 좌표 등록 완료 상태로 사용하지
말고, 서버가 제공하는 실제 완료 필드를 사용하도록 해당 행의 아이콘과 문구를 수정하세요. 행사 정보가 로드되기 전에는 완료/해당 없음 대신
기존 로딩 상태를 표시하세요. approvalRequestReady 계산에서도 동일한 서버 검증 완료 조건을 포함해 평면도 승인 조건과 화면
상태가 일치하도록 업데이트하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7964d4ff-961a-4423-b979-6f27ddd39226

📥 Commits

Reviewing files that changed from the base of the PR and between 249cb6b and 7481dd4.

📒 Files selected for processing (11)
  • BE/src/main/java/com/min/edu/advertisement/domain/Advertisement.java
  • BE/src/main/java/com/min/edu/advertisement/repository/AdvertisementRepository.java
  • BE/src/main/java/com/min/edu/advertisement/service/AdvertisementService.java
  • BE/src/main/java/com/min/edu/event/dto/EventDtos.java
  • BE/src/main/java/com/min/edu/event/repository/EventRepository.java
  • BE/src/main/java/com/min/edu/event/service/EventLifecycleScheduler.java
  • BE/src/main/resources/db/migration/V7__add_lifecycle_query_indexes.sql
  • BE/src/test/java/com/min/edu/advertisement/domain/AdvertisementTest.java
  • FE/src/pages/Home.jsx
  • FE/src/pages/OrganizerAdmin.jsx
  • FE/src/pages/PlatformAdmin.jsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • BE/src/main/java/com/min/edu/event/service/EventLifecycleScheduler.java
  • BE/src/main/java/com/min/edu/advertisement/domain/Advertisement.java
  • FE/src/pages/Home.jsx
  • BE/src/main/java/com/min/edu/event/dto/EventDtos.java
  • FE/src/pages/PlatformAdmin.jsx
  • BE/src/main/java/com/min/edu/advertisement/service/AdvertisementService.java

Comment on lines +1 to +8
CREATE INDEX IF NOT EXISTS idx_events_status_end_at
ON events (status, end_at);

CREATE INDEX IF NOT EXISTS idx_advertisements_status_start_at
ON advertisements (status, start_at);

CREATE INDEX IF NOT EXISTS idx_advertisements_status_end_at
ON advertisements (status, end_at);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Flyway의 트랜잭션 실행 설정과 PostgreSQL 사용 여부를 확인합니다.
rg -n -i -C3 'flyway|execute.?in.?transaction|jdbc:postgresql|postgresql' BE

# 이 마이그레이션의 인덱스 생성 문을 확인합니다.
sed -n '1,20p' BE/src/main/resources/db/migration/V7__add_lifecycle_query_indexes.sql

Repository: half-fifty/eventoday

Length of output: 6090


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Flyway and datasource configuration files ---'
git ls-files BE | rg -i '(^|/)(application|bootstrap|flyway|gradle|docker|compose|helm|values|deployment|.*ya?ml|.*properties|.*conf|.*toml)' || true

echo '--- Flyway-related configuration and migration settings ---'
rg -n -i -C4 'spring\.flyway|flyway\.|execute.?in.?transaction|migrate|migration|DB_URL|POSTGRES' BE --glob '!build/**' || true

echo '--- Migration inventory and relevant schema definitions ---'
git ls-files BE/src/main/resources/db/migration | sort
rg -n -i -C3 'CREATE TABLE (events|advertisements)|status|start_at|end_at' BE/src/main/resources/db/migration --glob '*.sql' || true

echo '--- Deployment ordering and application startup ---'
rg -n -i -C4 'flyway|migration|migrate|spring\.boot|java -jar|docker compose|healthcheck|readiness|liveness' . --glob '!BE/build/**' --glob '!node_modules/**' || true

Repository: half-fifty/eventoday

Length of output: 33083


🌐 Web query:

PostgreSQL official documentation CREATE INDEX lock writes CREATE INDEX CONCURRENTLY transaction block

💡 Result:

In PostgreSQL, the locking behavior and transaction requirements for creating indexes differ significantly depending on whether the CONCURRENTLY option is used. Standard CREATE INDEX A standard index build acquires a ShareLock on the table [1][2]. This lock allows concurrent reads but blocks all writes (INSERT, UPDATE, DELETE) on the table until the index build is complete [1][3]. This operation can be performed within a transaction block [4]. CREATE INDEX CONCURRENTLY When the CONCURRENTLY option is specified, PostgreSQL builds the index without acquiring a lock that prevents concurrent writes [1][4]. Instead, it acquires a ShareUpdateExclusiveLock [5][2]. This lock mode allows concurrent INSERT, UPDATE, and DELETE operations, as well as concurrent reads [2]. Key Requirements and Caveats: 1. Cannot Run in a Transaction Block: Unlike a standard index build, the CREATE INDEX CONCURRENTLY command cannot be executed within an explicit transaction block (e.g., it cannot be wrapped in a BEGIN... COMMIT block) [2][4]. If you attempt to do so, PostgreSQL will raise an error [2]. 2. Increased Resource Usage: Because the operation must perform two scans of the table and wait for existing transactions to terminate, it requires more total work and takes significantly longer to complete than a standard build [1][4]. 3. Performance Impact: While it allows production traffic to continue, the sustained CPU and I/O load associated with the index creation may still impact database performance [1][4]. For full details, refer to the official PostgreSQL documentation on the CREATE INDEX command [1][6].

Citations:


🌐 Web query:

Flyway official documentation executeInTransaction false Spring Boot spring.flyway.execute-in-transaction

💡 Result:

The configuration property spring.flyway.execute-in-transaction in Spring Boot corresponds to Flyway's executeInTransaction setting [1][2][3]. This property controls whether Flyway executes SQL migrations within a database transaction [4][5]. When spring.flyway.execute-in-transaction is set to false, Flyway will attempt to execute migration scripts outside of a transaction [5]. This is commonly required for databases or specific migration statements (such as certain DDL commands in PostgreSQL or SQL Server) that cannot be executed within a transaction block [4]. Historically, there was an alignment issue where the default value in Spring Boot's FlywayProperties did not match Flyway's native default [6]. This was subsequently addressed to ensure consistency [6]. You can configure this setting in your application.properties or application.yml file as follows: In application.properties: spring.flyway.execute-in-transaction=false In application.yml: spring: flyway: execute-in-transaction: false Note that this configuration is a global setting for Flyway managed by Spring Boot [1][3]. If you need more granular control, such as handling specific migrations differently, you may need to rely on Flyway's native script-level configuration or Java migration interface methods like canExecuteInTransaction [4][5].

Citations:


온라인 인덱스 생성으로 변경하세요.

운영 환경에서 일반 CREATE INDEX는 인덱스 생성 중 대상 테이블의 쓰기를 차단할 수 있습니다. 이 마이그레이션을 비트랜잭션으로 실행하고 세 인덱스를 CREATE INDEX CONCURRENTLY로 생성하세요. CONCURRENTLY는 트랜잭션 블록에서 실행할 수 없습니다.

🧰 Tools
🪛 Squawk (2.61.0)

[warning] 1-2: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.

(require-concurrent-index-creation)


[warning] 4-5: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.

(require-concurrent-index-creation)


[warning] 7-8: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.

(require-concurrent-index-creation)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BE/src/main/resources/db/migration/V7__add_lifecycle_query_indexes.sql`
around lines 1 - 8, 마이그레이션의 세 인덱스 생성문을 모두 CREATE INDEX CONCURRENTLY로 변경하고, 해당
마이그레이션이 트랜잭션 없이 실행되도록 비트랜잭션 마이그레이션 설정을 추가하세요. 기존 IF NOT EXISTS 동작과 인덱스 이름은
유지하세요.

Sources: Path instructions, Linters/SAST tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (3)
FE/src/pages/EventForm.jsx (3)

117-132: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

저장 요청에 진행 중 가드를 추가하세요.

setSaving(true)는 렌더링 후 버튼을 비활성화합니다. Form onSubmit은 Enter 키나 반복 제출으로 다시 실행될 수 있습니다. eventApi.create는 POST 요청이므로 중복 호출 시 행사가 중복 생성될 수 있습니다. 수정 요청도 중복 실행될 수 있습니다.

useRef로 진행 중 상태를 즉시 기록하세요. preventDefault()를 먼저 호출한 뒤 중복 요청을 반환하고, finally에서 ref와 상태를 함께 초기화하세요.

수정 예시
+  const savingRef = useRef(false);
+
   const submit = async (e) => {
     e.preventDefault();
+    if (savingRef.current) return;
+    savingRef.current = true;
     if (!organizationId) {
+      savingRef.current = false;
       setError("행사를 등록할 운영 조직을 선택해 주세요.");
       return;
     }
     ...
-    finally { setSaving(false); }
+    finally {
+      savingRef.current = false;
+      setSaving(false);
+    }

As per path instructions, “중복 요청과 동시성 문제”를 우선 확인했습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/EventForm.jsx` around lines 117 - 132, Update the submit
function to use a useRef-based in-progress guard: call preventDefault first,
return immediately when the ref indicates a request is active, and set the ref
before starting the create/update request. In the existing finally block, clear
both the ref and saving state so subsequent submissions remain possible while
preventing concurrent eventApi.create or eventApi.update calls.

Source: Path instructions


46-64: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

조직 조회 Effect의 오래된 응답을 무효화하세요.

requestedOrganizationId가 변경되면 이 Effect가 다시 실행됩니다. 현재 이전 요청의 then과 finally가 상태를 계속 갱신합니다. 응답 순서가 뒤집히면 오래된 요청이 현재 organizationId를 덮어쓸 수 있습니다. 재실행 시 organizationLoading을 true로 설정하지 않고 organizationLoadFailed도 초기화하지 않습니다.

Effect에 취소 플래그 또는 요청 시퀀스를 추가하세요. 재실행 시작 시 로딩·실패 상태를 초기화하세요.

수정 예시
   useEffect(() => {
+    let cancelled = false;
+    setOrganizationLoading(true);
+    setOrganizationLoadFailed(false);
     eventApi.managedOrganizations()
       .then((result) => {
+        if (cancelled) return;
         const available = result?.data || [];
         setOrganizations(available);
         ...
       })
       .catch((requestError) => {
+        if (cancelled) return;
         setOrganizationLoadFailed(true);
         ...
       })
-      .finally(() => setOrganizationLoading(false));
+      .finally(() => {
+        if (!cancelled) setOrganizationLoading(false);
+      });
+    return () => { cancelled = true; };
   }, [requestedOrganizationId]);

As per path instructions, “중복 요청과 동시성 문제” 및 “예외 처리 누락”을 우선 확인했습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/EventForm.jsx` around lines 46 - 64, Update the useEffect keyed
by requestedOrganizationId to invalidate prior requests with a cancellation flag
or request sequence, and guard every then, catch, and finally state update so
stale responses cannot overwrite current organization data. At each effect
rerun, reset organizationLoading to true and organizationLoadFailed to false,
and preserve cleanup invalidation for the previous request.

Source: Path instructions


122-126: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

날짜 변환을 try 내부에서 검증하세요.

toOffsetDateTime는 유효하지 않은 비어 있지 않은 값에서 RangeError를 발생시킵니다. 현재 payload 생성이 setSaving(true) 이후 try 밖에 있으므로, 예외가 발생하면 catch와 finally가 실행되지 않습니다. 저장 버튼이 계속 비활성화되고 오류도 표시되지 않습니다. 날짜 비교도 잘못된 값을 거부하지 않습니다.

payload 생성 전에 모든 비어 있지 않은 날짜의 getTime()이 NaN인지 검사하고, payload 생성과 검증을 try 안으로 이동하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/EventForm.jsx` around lines 122 - 126, Move payload construction
and date validation inside the existing try block after setSaving(true) in the
EventForm submit flow. Before calling toOffsetDateTime, validate every non-empty
date value via getTime() and reject NaN values so invalid dates follow the
existing error path; preserve catch/finally execution and ensure all date fields
are validated consistently.

Source: Path instructions

♻️ Duplicate comments (1)
FE/src/pages/EventForm.jsx (1)

103-114: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

수동 주소 입력이 우편번호 응답에 덮어쓰이지 않게 하세요.

postalCodeSequence는 selectPlace에서만 증가합니다. 사용자가 장소 선택 직후 address 또는 postalCode를 수정해도 Line 112의 응답은 같은 시퀀스로 적용됩니다. 이 경우 주소와 우편번호가 서로 다른 장소를 가리키거나 사용자의 우편번호 입력이 사라집니다.

주소·우편번호의 수동 변경 시 시퀀스를 증가시키세요. 또는 응답 적용 전에 현재 주소가 selectedAddress와 같은지 확인하세요.

이전 리뷰에서 선택 간 오래된 우편번호 응답은 수정되었지만 수동 편집 경로는 아직 보호되지 않습니다. As per path instructions, “중복 요청과 동시성 문제”를 우선 확인했습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/EventForm.jsx` around lines 103 - 114, Update the postal-code
synchronization around selectPlace and the form field change handlers so manual
edits to address or postalCode invalidate the pending postalCodeSequence
request. Ensure the asynchronous response in selectPlace applies only when its
sequence remains current and the user has not changed the relevant fields,
preserving manual address and postal-code values.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@FE/src/pages/EventForm.jsx`:
- Around line 87-101: Update the place-query change handler and the short-query
branch in searchPlaces to increment placeSearchSequence.current and clear
places, invalidating any in-flight request before processing new input. In the
short-query branch, also setSearching(false) before returning, while preserving
the existing validation error behavior.
- Around line 66-84: Update the useEffect loading transitions so an event or
organization change cannot display stale form data: call setLoading(false) when
!eventId causes an early return, and call setLoading(true) after
organizationLoading has cleared and immediately before starting
eventApi.managedDetail. Preserve the existing cancellation handling and loading
completion behavior.

In `@FE/src/pages/EventMembers.jsx`:
- Line 13: Replace the single pendingMemberId state with a Set or object of
pending member IDs in EventMembers. Update rendering and both toggle/remove
request handlers to check the current member ID, add it when the request starts,
and remove only that ID in each request’s finally block so concurrent member
requests do not overwrite or re-enable one another.

---

Outside diff comments:
In `@FE/src/pages/EventForm.jsx`:
- Around line 117-132: Update the submit function to use a useRef-based
in-progress guard: call preventDefault first, return immediately when the ref
indicates a request is active, and set the ref before starting the create/update
request. In the existing finally block, clear both the ref and saving state so
subsequent submissions remain possible while preventing concurrent
eventApi.create or eventApi.update calls.
- Around line 46-64: Update the useEffect keyed by requestedOrganizationId to
invalidate prior requests with a cancellation flag or request sequence, and
guard every then, catch, and finally state update so stale responses cannot
overwrite current organization data. At each effect rerun, reset
organizationLoading to true and organizationLoadFailed to false, and preserve
cleanup invalidation for the previous request.
- Around line 122-126: Move payload construction and date validation inside the
existing try block after setSaving(true) in the EventForm submit flow. Before
calling toOffsetDateTime, validate every non-empty date value via getTime() and
reject NaN values so invalid dates follow the existing error path; preserve
catch/finally execution and ensure all date fields are validated consistently.

---

Duplicate comments:
In `@FE/src/pages/EventForm.jsx`:
- Around line 103-114: Update the postal-code synchronization around selectPlace
and the form field change handlers so manual edits to address or postalCode
invalidate the pending postalCodeSequence request. Ensure the asynchronous
response in selectPlace applies only when its sequence remains current and the
user has not changed the relevant fields, preserving manual address and
postal-code values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 93dabba7-3054-400c-8d03-7fba41ca2e58

📥 Commits

Reviewing files that changed from the base of the PR and between 7481dd4 and a4af329.

📒 Files selected for processing (11)
  • BE/src/main/java/com/min/edu/advertisement/repository/AdvertisementRepository.java
  • BE/src/main/java/com/min/edu/advertisement/service/AdvertisementService.java
  • BE/src/main/java/com/min/edu/common/exception/BusinessException.java
  • BE/src/main/java/com/min/edu/common/exception/GlobalErrorCode.java
  • BE/src/main/java/com/min/edu/event/domain/Event.java
  • BE/src/main/java/com/min/edu/event/service/LocationService.java
  • BE/src/test/resources/application.properties
  • FE/src/components/KakaoMapPreview.jsx
  • FE/src/pages/EventDetail.jsx
  • FE/src/pages/EventForm.jsx
  • FE/src/pages/EventMembers.jsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • BE/src/main/java/com/min/edu/advertisement/repository/AdvertisementRepository.java
  • BE/src/main/java/com/min/edu/event/service/LocationService.java
  • FE/src/pages/EventDetail.jsx
  • BE/src/main/java/com/min/edu/event/domain/Event.java
  • FE/src/components/KakaoMapPreview.jsx
  • BE/src/main/java/com/min/edu/advertisement/service/AdvertisementService.java

Comment thread FE/src/pages/EventForm.jsx
Comment thread FE/src/pages/EventForm.jsx
Comment thread FE/src/pages/EventMembers.jsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@FE/src/pages/EventMembers.jsx`:
- Around line 16-29: Update the EventMembers request flow so results from load
started for a previous eventId cannot update members or error after navigation;
apply those state updates only when the captured eventId still matches the
current eventId. Change pendingMemberIds tracking in beginMemberRequest and
finishMemberRequest, plus their callers, to use a composite eventId/memberId key
so identical member IDs across events do not collide.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d59bb78c-dc54-4b2d-9bfd-2c328db94137

📥 Commits

Reviewing files that changed from the base of the PR and between a4af329 and 0cf5d30.

📒 Files selected for processing (2)
  • FE/src/pages/EventForm.jsx
  • FE/src/pages/EventMembers.jsx

Comment thread FE/src/pages/EventMembers.jsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@FE/src/pages/EventMembers.jsx`:
- Around line 58-70: Update the add function to guard against concurrent
submissions using an add-request in-flight ref, set the corresponding loading
state before calling eventApi.addMember, and return early when a request is
already active. Disable the add button while that loading state is true, and
clear both the ref and state in finally so the control is restored after success
or failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 36fc30e5-d1b1-4fd4-835b-48b77a6bd93d

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf5d30 and fe30313.

📒 Files selected for processing (1)
  • FE/src/pages/EventMembers.jsx

Comment on lines +58 to +70
const add = async (e) => {
e.preventDefault(); setError("");
const requestEventId = eventId;
try {
await eventApi.addMember(requestEventId, { memberId: Number(memberId), eventRole });
if (currentEventIdRef.current === requestEventId) setMemberId("");
await load(requestEventId);
} catch (requestError) {
if (currentEventIdRef.current === requestEventId) {
setError(requestError.message || "담당자를 추가하지 못했습니다.");
}
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

담당자 추가 요청도 처리 중 중복 제출을 차단하세요.

add는 진행 상태를 확인하지 않습니다. 사용자가 추가 버튼을 빠르게 두 번 누르면 eventApi.addMember 요청이 두 번 전송됩니다. 서버가 중복을 허용하면 담당자가 중복 등록될 수 있습니다. 서버가 거부해도 불필요한 오류가 표시됩니다.

추가 요청용 진행 상태를 ref와 state로 관리하세요. 요청 중에는 추가 버튼을 비활성화하세요. finally에서 해당 상태를 해제하세요.

수정 예시
+  const [isAdding, setIsAdding] = useState(false);
+  const isAddingRef = useRef(false);

   const add = async (e) => {
     e.preventDefault(); setError("");
+    if (isAddingRef.current) return;
+    isAddingRef.current = true;
+    setIsAdding(true);
     const requestEventId = eventId;
     try {
       await eventApi.addMember(requestEventId, { memberId: Number(memberId), eventRole });
       if (currentEventIdRef.current === requestEventId) setMemberId("");
       await load(requestEventId);
     } catch (requestError) {
       if (currentEventIdRef.current === requestEventId) {
         setError(requestError.message || "담당자를 추가하지 못했습니다.");
       }
+    } finally {
+      isAddingRef.current = false;
+      setIsAdding(false);
     }
   };
-        <button className="h-10 px-lg bg-primary text-white rounded-full">추가</button>
+        <button disabled={isAdding} className="h-10 px-lg bg-primary text-white rounded-full disabled:opacity-50">추가</button>

As per path instructions, “중복 요청과 동시성 문제”를 우선 확인했습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FE/src/pages/EventMembers.jsx` around lines 58 - 70, Update the add function
to guard against concurrent submissions using an add-request in-flight ref, set
the corresponding loading state before calling eventApi.addMember, and return
early when a request is already active. Disable the add button while that
loading state is true, and clear both the ref and state in finally so the
control is restored after success or failure.

Source: Path instructions

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.

1 participant