Skip to content

✨ [FEAT] 부스 관리 기능 구현 (WBS-067~077, WBS-186~190) - #21

Merged
hyun326 merged 14 commits into
devfrom
feature/booth-management
Aug 4, 2026
Merged

hyun326 merged 14 commits into
devfrom
feature/booth-management

Conversation

@hyun326

@hyun326 hyun326 commented Aug 4, 2026 •

Copy link
Copy Markdown
Collaborator

📄 작업 내용

무엇을, 왜 변경했는지 설명해주세요.

부스(Booth) 관리 기능을 백엔드/프론트엔드 전체 구간에 걸쳐 구현했습니다.

  • 부스 CRUD API: 목록/상세/등록/일괄등록/수정/상태변경/삭제 (WBS-067~073)
  • 배정된 부스의 소개(부스명·한줄소개·상세설명·전시내용) 수정 API (WBS-074)
  • 부스 QR 코드 발급/조회 API — 재발급 시 새 토큰을 만들지 않고 기존 토큰을 그대로 반환하는 멱등 설계 (WBS-075~076)
  • 부스 검색(층/구역/키워드) 및 페이지네이션 (WBS-077)
  • 위 API에 대응하는 개최자센터 프론트엔드 "부스 관리" 화면 (WBS-186~190) — 등록/일괄등록/수정/상태변경/삭제/소개 수정/QR 발급·조회·닫기 토글
  • QR 코드를 실제 스캔 가능한 이미지로 렌더링 (부스 상세 페이지 URL을 인코딩)
  • 모집 공고(BoothRecruitment)가 OPEN 상태가 된 이후에는 모집 종료일만 수정 가능하도록 제한 (그 외 내용 수정 차단), 자동 상태 전환 스케줄러 테스트 보강
  • 메인 화면 "현재 모집 중인 부스 공고" 섹션과 모집 공고 상세 페이지를 실제 데이터로 연동, 비로그인 사용자도 조회 가능한 부스 공개 API 추가, 부스 카드 클릭 시 규격·설비·가격 등 스펙을 팝업으로 확인 가능
  • 개최자센터의 "부스 관리"·"부스 모집 공고" 화면이 eventId를 직접 입력하던 방식에서 벗어나, 상단에서 실제 담당 행사를 선택하면 자동으로 연동되도록 개선. 개최자센터 진입 시 로그인 계정이 속한 조직을 자동 조회해 organizationId 쿼리 파라미터 없이도 접근 가능
  • 부스 API 인증 규칙 누락으로 미인증 요청 시 500이 발생하던 버그 수정 (401로 정상 처리되도록)

🔗 관련 이슈

Closes #

☑️ 체크리스트

  • 로컬에서 정상 동작 확인
  • 테스트 작성 및 통과 (백엔드 전체 테스트 통과, 프론트 빌드 통과)
  • 불필요한 로그/주석/커밋 정리

📸 스크린샷 (UI 변경 시)

개최자센터 부스 관리 화면, 모집 공고 상세 페이지의 부스 목록/스펙 팝업 등 UI 변경 포함

💬 리뷰어에게

dev를 두 차례 병합하며 최신화했습니다. 부스 신청(BoothApplication) 기능은 이번 범위에서 제외했습니다.

Summary by CodeRabbit

  • 새로운 기능
    • 행사별 부스를 단일 또는 일괄 등록하고 수정·삭제·상태 변경할 수 있습니다.
    • 상태·위치·키워드 필터와 페이지 단위 조회를 지원합니다.
    • 부스 소개 정보와 QR 코드를 발급·조회할 수 있습니다.
    • 공개 모집 상세에서 부스 목록, 규격·설비·가격 및 상세 정보를 확인할 수 있습니다.
    • 모집 종료일을 별도로 수정할 수 있습니다.
  • 개선
    • 관리자 화면에서 행사와 조직을 선택해 모집 및 부스를 통합 관리할 수 있습니다.
    • 홈 화면에 최신 공개 모집 공고가 표시됩니다.
    • 입력값 검증과 권한·상태별 안내가 강화되었습니다.

cfu344-png and others added 12 commits August 4, 2026 13:21
- BEFORE_OPEN에서만 전체 수정 허용, OPEN에서는 종료일 전용 API로만 연장/단축 가능
- 스케줄러의 시간 기반 자동 상태전환(BEFORE_OPEN→OPEN→CLOSED) 테스트 보강

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WBS-067~073: 개최자용 부스 CRUD API. 목록은 상태·층·구역 필터와 페이지네이션을 지원하고,
삭제는 AVAILABLE 상태의 미사용 부스만 허용한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
스케줄러의 상태 전환 로직이 REQUIRES_NEW로 별도 트랜잭션을 여는데, 테스트 클래스 전체를
@transactional로 감싸면 테스트에서 저장한 데이터가 아직 커밋되지 않아 스케줄러가 보지 못했다.
@transactional을 제거하고 각 테스트 후 직접 데이터를 정리하도록 변경.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WBS-074~077:
- 배정된 참가기업(조직 구성원)이 ASSIGNED 상태 부스의 소개(대표 이미지 등)를 수정할 수 있음
- 개최자가 부스 QR 토큰을 발급/재발급, 개최자와 배정된 조직 구성원이 조회 가능
- 부스 목록 API에 부스명·기업명·부스번호 키워드 검색 추가

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SecurityConfig에 /events/*/booths/** 경로가 빠져 있어 기본값(anyRequest permitAll)을
타면서 인증 없이 컨트롤러까지 들어와 member가 null이 되어 NPE(500)가 발생했음.
authenticated() 규칙을 추가해 401로 정상 처리되도록 수정.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- BoothManagementPanel: 목록/필터/검색/페이지네이션, 단건·일괄 등록, 수정,
  상태변경, 삭제, 배정 부스 소개 관리, QR 발급·조회를 한 화면에서 처리
- OrganizerAdmin의 "부스 관리" 탭에 연결(기존 목데이터 그리드 제거)
- QR은 스캔 시 부스 상세로 이동하도록 URL을 인코딩해 qrcode로 실제 이미지 렌더링
- QR 발급을 멱등하게 변경: 이미 발급된 QR이 있으면 새로 만들지 않고 기존 것을
  그대로 반환(분실 시에는 재조회로 충분하며, 실수로 기존 QR을 무효화하는 것을 방지)
- "QR 보기" 버튼이 열려있는 부스에서는 "QR 닫기"로 토글

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
메인 화면 모집중 부스 공고 섹션과 모집 공고 상세 페이지를 실제 데이터로 연동하고,
비로그인 사용자도 조회 가능한 부스 공개 조회 API를 추가했다. 부스 카드를 클릭하면
규격/면적/설비 지원 여부 등 스펙을 팝업으로 확인할 수 있다.
# Conflicts:
#	BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java
#	BE/src/main/java/com/min/edu/common/exception/GlobalErrorCode.java
행사 등록 화면이 없던 시절 임시로 쓰던 eventId 수동 입력을 제거하고,
개최자센터 상단에서 관리 중인 행사를 선택하면 부스 관리·모집 공고
패널이 자동으로 해당 행사 데이터를 불러오도록 연동했다. 또한
개최자센터 진입 시 로그인 계정이 속한 조직을 자동으로 조회해
organizationId 쿼리 파라미터 없이도 접근할 수 있도록 했다.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d885eb3-ec08-47b9-bf9f-cf431073fbf7

📥 Commits

Reviewing files that changed from the base of the PR and between 29d58e3 and 3a522f7.

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

Walkthrough

부스 CRUD, 공개 조회, 소개 관리, QR 발급 API와 관리자 화면을 추가했습니다. 공개 모집과 부스 상세 화면도 API 데이터를 사용합니다. OPEN 모집은 종료 시각만 수정하도록 변경했습니다.

Changes

부스 관리 기능

Layer / File(s) Summary
부스 계약과 도메인 모델
BE/src/main/java/com/min/edu/booth/{controller,domain,dto,repository}/*, BE/src/main/java/com/min/edu/common/config/SecurityConfig.java
부스 요청·응답 DTO, 도메인 메서드, 검색 저장소, CRUD·QR 엔드포인트와 공개 접근 규칙을 추가했습니다.
부스 서비스와 권한 흐름
BE/src/main/java/com/min/edu/booth/service/*, BE/src/main/java/com/min/edu/booth/support/*, BE/src/main/java/com/min/edu/common/exception/*, BE/src/main/java/com/min/edu/file/service/FileService.java, BE/src/main/java/com/min/edu/organization/repository/*, BE/src/test/java/com/min/edu/booth/*
부스 생명주기, 권한·상태 검증, QR 토큰 중복 확인, 파일 접근 검증, 장비 JSON 변환과 통합 테스트를 추가했습니다.
모집 종료 시각 수정
BE/src/main/java/com/min/edu/recruitment/*, BE/src/test/java/com/min/edu/recruitment/*
OPEN 상태에서 종료 시각만 수정하는 API를 추가했습니다. 모집 상태 제한과 스케줄러 전환 테스트를 보강했습니다.
관리자 부스 화면
FE/src/api/boothApi.js, FE/src/components/BoothManagementPanel.jsx, FE/src/pages/OrganizerAdmin.jsx, FE/src/components/RecruitmentManagementPanel.jsx, FE/package.json
부스 필터, 페이징, 등록, 수정, 삭제, 소개 관리와 QR 표시를 추가했습니다. 행사·조직 선택과 모집 종료 시각 수정도 연결했습니다.
공개 조회 화면
FE/src/pages/Home.jsx, FE/src/pages/RecruitmentDetail.jsx
공개 모집 공고와 공개 부스 목록을 API에서 조회합니다. 부스 상세 정보를 모달로 표시합니다.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant 관리자
  participant BoothManagementPanel
  participant boothApi
  participant BoothController
  participant BoothService
  participant BoothRepository
  관리자->>BoothManagementPanel: 부스 생성 또는 QR 발급 실행
  BoothManagementPanel->>boothApi: 부스 API 요청
  boothApi->>BoothController: HTTP 요청 전송
  BoothController->>BoothService: 인증 회원과 요청 데이터 전달
  BoothService->>BoothRepository: 부스 조회 및 저장
  BoothService-->>BoothController: 부스 응답 반환
  BoothController-->>boothApi: JSON 응답 반환
  boothApi-->>BoothManagementPanel: 결과 전달
  BoothManagementPanel-->>관리자: 목록 또는 QR 정보 표시
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% 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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/booth-management

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

@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: 17

🧹 Nitpick comments (11)
BE/src/test/java/com/min/edu/recruitment/controller/BoothRecruitmentControllerTest.java (1)

340-361: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

종료 시각 변경값을 검증하세요.

Line 359와 Line 360은 성공 상태와 OPEN 상태만 검사합니다. updateEndAt이 종료 시각을 저장하지 않거나 이전 값을 반환해도 이 테스트는 통과합니다.

응답의 recruitmentEndAt를 newEnd와 비교하세요. 필요하면 영속성 컨텍스트를 비운 뒤 관리 조회로 저장값도 확인하세요.

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
`@BE/src/test/java/com/min/edu/recruitment/controller/BoothRecruitmentControllerTest.java`
around lines 340 - 361, Update the test method OPEN_상태에서는_모집_종료일만_수정할_수_있다 to
assert that the response recruitmentEndAt matches newEnd, in addition to the
existing success and OPEN status checks. If the response is insufficient, clear
the persistence context and verify the stored value through a managed retrieval.

Source: Path instructions

BE/src/main/java/com/min/edu/booth/domain/Booth.java (1)

121-191: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

동일 타입 파라미터가 길게 이어져서 순서 실수를 잡을 수 없습니다.

create는 16개, updateDetails는 15개 파라미터를 위치 인자로 받습니다. floorName, zoneName, locationDescription은 모두 String이고, widthMeter, depthMeter, areaSqm은 모두 BigDecimal이며, 설비 4개는 모두 boolean입니다. 호출부에서 순서를 바꿔 넘겨도 컴파일 오류가 나지 않고, 잘못된 값이 그대로 저장됩니다. 지금 BoothService.create, createBulk, update 세 곳에서 같은 순서를 반복해서 넘기고 있어서 앞으로 필드가 추가될 때 실수가 생기기 쉽습니다.

이미 @Builder가 있으니 위치 인자 대신 값 객체(예: 위치 정보, 설비 정보, 규격 정보를 묶은 작은 record)를 파라미터로 받는 방식을 권합니다. 급하지 않으니 후속 작업으로 처리해도 됩니다.

🤖 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/booth/domain/Booth.java` around lines 121 - 191,
Reduce positional-argument risk in Booth.create and Booth.updateDetails by
grouping the related same-type fields into dedicated value objects, such as
records for location, dimensions, and equipment, and update the
BoothService.create, createBulk, and update callers to pass those objects.
Preserve the existing field assignments and behavior while removing the long
repeated parameter lists.
BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java (1)

22-39: 🧹 Nitpick | 🔵 Trivial

검색 쿼리가 인덱스를 타지 못합니다. 데이터가 늘어나면 느려집니다.

keyword는 %...% 형태로 들어오므로 LOWER(b.boothCode) LIKE :keyword와 LOWER(b.displayName) LIKE :keyword는 인덱스를 사용할 수 없습니다. 여기에 Organization을 대상으로 하는 EXISTS 서브쿼리가 OR로 붙어 있어서, 부스 행마다 서브쿼리 평가가 발생합니다. 페이지 조회는 count 쿼리까지 같은 조건으로 두 번 실행됩니다.

행사당 부스 수가 수백 건 수준이면 지금 형태로 충분합니다. 규모가 커질 계획이면 아래를 고려해 주세요.

  • booths(event_id, status), booths(event_id, floor_name, zone_name) 복합 인덱스 추가
  • 조직명 검색은 OR 대신 LEFT JOIN Organization o ON o.id = b.assignedOrganizationId 로 바꿔 서브쿼리 반복 평가 제거
  • 키워드 검색이 핵심 기능이 되면 PostgreSQL pg_trgm GIN 인덱스 또는 별도 검색 컬럼 도입

참고로 정적 분석이 이 부분을 LDAP 인젝션으로 표시했지만, 명명 파라미터 바인딩을 쓰고 있어서 오탐입니다.

🤖 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/booth/repository/BoothRepository.java` around
lines 22 - 39, Update the BoothRepository.search query strategy for scalable
keyword filtering: replace the organization-name EXISTS condition with a LEFT
JOIN to Organization while preserving the existing filters and results, add the
recommended composite indexes for event/status and event/floorName/zoneName, and
use PostgreSQL pg_trgm GIN indexes or a dedicated searchable column if keyword
search is a core requirement. Keep the named parameter binding unchanged.

Source: Linters/SAST tools

BE/src/main/java/com/min/edu/booth/service/BoothService.java (2)

216-222: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

상세 조회 권한이 소개 수정 권한보다 좁습니다.

getDetail은 requireEventManager만 통과시킵니다. 반면 updateIntro(231행)와 getQr(266행)은 배정 조직 멤버에게도 열려 있습니다. 결과적으로 배정 조직 멤버는 자기 부스의 소개를 수정할 수 있지만, 현재 등록된 소개 값을 읽을 수는 없습니다. 수정 화면에 기존 값을 채워 넣지 못합니다.

getDetail도 requireAssignedOrganizationMemberOrEventManager를 쓰거나, 조직 멤버용 조회 API를 따로 두는 편이 자연스럽습니다. 단, toResponse에는 qrToken이 포함되므로 권한을 넓힐 때 응답 필드 범위도 함께 정해 주세요.

🤖 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/booth/service/BoothService.java` around lines
216 - 222, Update BoothService.getDetail to allow assigned organization members
as well as event managers, matching the authorization used by updateIntro and
getQr, or provide an equivalent organization-member detail path. Because
toResponse includes qrToken, ensure the expanded-access response excludes or
safely handles that field according to the intended permission boundary.

462-475: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

파싱 실패 원인을 로그로 남겨 주세요.

fromJson이 잘못된 basic_equipment 값을 빈 리스트로 변환하면 응답이 실제 데이터 오류를 숨깁니다. @Slf4j를 추가하고 catch 블록에서 log.warn(..., e)를 호출해 주세요. TypeReference를 사용할 경우 패키지는 tools.jackson.core.type.TypeReference이며, objectMapper.readValue(json, new TypeReference<List<String>>() {})로 작성할 수 있습니다.

🤖 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/booth/service/BoothService.java` around lines
462 - 475, Update BoothService.fromJson to log parsing failures before returning
an empty list: add the Lombok `@Slf4j` logger and call log.warn with descriptive
context and the caught JacksonException in the catch block. Preserve the
existing empty-list fallback for invalid JSON and blank or null input.
BE/src/main/java/com/min/edu/common/config/SecurityConfig.java (1)

68-69: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Broken Authentication (CWE-306): Missing Authentication for Critical Function

Reachability: External

서비스 계층에 인증 가드를 추가하세요.

현재 /events/*/booths/** 규칙은 BoothController의 모든 보호 경로를 포함하며, 공개 경로보다 먼저 적용됩니다. 다만 anyRequest().permitAll() 상태에서 경로가 규칙에서 누락되면 BoothService의 member 역참조가 NullPointerException을 발생시켜 500 응답을 반환합니다.

requireEventManager와 requireAssignedOrganizationMemberOrEventManager의 시작부에서 member == null이면 BusinessException(GlobalErrorCode.UNAUTHORIZED)를 던지도록 공통 가드를 추가하세요.

🤖 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/common/config/SecurityConfig.java` around lines
68 - 69, In BoothService, add a shared guard at the start of requireEventManager
and requireAssignedOrganizationMemberOrEventManager that throws
BusinessException(GlobalErrorCode.UNAUTHORIZED) when member is null, before any
member dereference or authorization checks.
FE/src/api/boothApi.js (1)

9-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

쿼리 문자열 생성 로직이 두 함수에 중복되어 있습니다.

listBooths와 listPublicBooths의 파라미터 필터링 코드가 완전히 같습니다. 나중에 필터 규칙(예: false 값 처리)을 바꿀 때 한쪽만 수정될 수 있습니다. 헬퍼로 빼면 됩니다.

♻️ 헬퍼 추출 예시
+const toQueryString = (params) => {
+  const query = new URLSearchParams(
+    Object.fromEntries(Object.entries(params).filter(([, value]) => value !== "" && value != null))
+  );
+  const queryString = query.toString();
+  return queryString ? `?${queryString}` : "";
+};
+
 const listBooths = async (eventId, params = {}) => {
-  const query = new URLSearchParams(
-    Object.fromEntries(Object.entries(params).filter(([, value]) => value !== "" && value != null))
-  );
-  const queryString = query.toString();
-  const response = await apiRequest(`/events/${eventId}/booths${queryString ? `?${queryString}` : ""}`);
+  const response = await apiRequest(`/events/${eventId}/booths${toQueryString(params)}`);
   return response.data;
 };
 
 const listPublicBooths = async (eventId, params = {}) => {
-  const query = new URLSearchParams(
-    Object.fromEntries(Object.entries(params).filter(([, value]) => value !== "" && value != null))
-  );
-  const queryString = query.toString();
-  const response = await apiRequest(`/events/${eventId}/booths/public${queryString ? `?${queryString}` : ""}`);
+  const response = await apiRequest(`/events/${eventId}/booths/public${toQueryString(params)}`);
   return response.data;
 };
🤖 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/api/boothApi.js` around lines 9 - 25, Extract the shared parameter
filtering and URLSearchParams construction from listBooths and listPublicBooths
into a reusable helper, then use that helper in both functions while preserving
the current handling of empty and nullish values.
BE/src/test/java/com/min/edu/booth/controller/BoothControllerTest.java (2)

164-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

벌크 UPDATE 전에 flush()를 명시해 주세요.

MockMvc로 생성한 부스는 같은 트랜잭션의 영속성 컨텍스트에 있습니다. 여기서 JPQL 벌크 UPDATE는 Hibernate의 자동 flush 동작에 의존합니다. flush 시점이 바뀌면 아직 INSERT되지 않은 부스를 갱신하려 하고, 테스트가 간헐적으로 실패합니다. 명시적으로 flush하면 순서가 확정됩니다.

♻️ 명시적 flush 추가
     private void assignToExhibitor(Long boothId) {
+        entityManager.flush();
         entityManager.createQuery(
                 "UPDATE Booth b SET b.assignedOrganizationId = :orgId WHERE b.id = :boothId")
🤖 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/test/java/com/min/edu/booth/controller/BoothControllerTest.java`
around lines 164 - 171, Update the assignToExhibitor method to explicitly flush
the EntityManager before executing the JPQL bulk UPDATE, ensuring
MockMvc-created Booth entities are inserted before the update runs. Keep the
existing parameters, executeUpdate call, and entityManager.clear() behavior
unchanged.

218-222: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

요청 JSON은 헬퍼로 만들고, 공개 조회 테스트도 추가하면 좋겠습니다.

new LinkedHashMap<String, Object>() {{ ... }} 패턴은 호출마다 익명 내부 클래스를 만들고 테스트 인스턴스를 캡처합니다. 동작에는 문제가 없지만 Map.of 또는 작은 빌더 헬퍼로 바꾸면 의도가 더 분명합니다.

추가로 프런트엔드가 사용하는 공개 목록 API(/events/{eventId}/booths/public)와 페이지 파라미터 검증(잘못된 page·size) 케이스는 이 테스트에 없습니다. 인증 없이 접근 가능한 경로이므로 테스트를 추가하는 편이 안전합니다.

필요하면 해당 테스트 케이스를 작성해 드릴까요?

🤖 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/test/java/com/min/edu/booth/controller/BoothControllerTest.java`
around lines 218 - 222, Replace the double-brace LinkedHashMap construction in
the bulk request test with Map.of or a focused request-body helper. Extend
BoothControllerTest with unauthenticated coverage for
/events/{eventId}/booths/public, including valid retrieval and validation
failures for invalid page and size parameters.
BE/src/main/java/com/min/edu/booth/dto/BoothIntroUpdateRequestDto.java (1)

18-20: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

description과 exhibitionContent에도 길이 상한을 두는 것이 좋겠습니다.

같은 DTO의 displayName과 shortIntro에는 상한이 있는데 이 두 필드만 무제한입니다. 부스 소개는 공개 조회 응답에 포함되므로, 상한이 없으면 매우 긴 본문이 저장되고 목록 응답 크기도 함께 커집니다.

♻️ 길이 상한 추가 예시
+    `@Size`(max = 5000)
     private String description;
 
+    `@Size`(max = 5000)
     private String exhibitionContent;
🤖 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/booth/dto/BoothIntroUpdateRequestDto.java`
around lines 18 - 20, Update BoothIntroUpdateRequestDto by adding appropriate
maximum-length validation constraints to description and exhibitionContent,
matching the existing validation style and limits used for displayName and
shortIntro. Keep the fields’ existing behavior unchanged apart from enforcing
these length limits.
BE/src/main/java/com/min/edu/booth/dto/BoothResponseDto.java (1)

41-42: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Sensitive Data Exposure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor

Reachability: Internal · Exploitability: Theoretical

QR 응답 DTO를 분리해 주세요. 현재 공개 API는 BoothPublicResponseDto를 사용하며, BoothResponseDto는 인증된 관리 API에서만 반환됩니다. 다만 qrToken과 qrIssuedAt을 공용 DTO에 포함하면 향후 재사용 시 민감한 값이 노출될 수 있으므로, QR 필드는 BoothQrResponseDto에서만 반환하는 구조가 안전합니다.

🤖 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/booth/dto/BoothResponseDto.java` around lines 41
- 42, Remove qrToken and qrIssuedAt from BoothResponseDto, and introduce or
reuse BoothQrResponseDto as the only DTO exposing those QR fields. Keep
BoothPublicResponseDto free of QR data and update QR-specific response mappings
or endpoints to return BoothQrResponseDto while preserving existing non-QR
response behavior.

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/booth/dto/BoothBulkCreateRequestDto.java`:
- Around line 18-19: Update the boothCodes field in BoothBulkCreateRequestDto to
add a `@Size` upper bound alongside `@NotEmpty`, using the project’s established
maximum batch-size constant or validation limit so oversized requests are
rejected before controller processing.

In `@BE/src/main/java/com/min/edu/booth/dto/BoothCreateRequestDto.java`:
- Around line 17-21: 등록 DTO의 문자열 필드에 최대 길이 검증을 추가하세요. BoothCreateRequestDto의
boothCode는 30자, boothType·floorName·zoneName은 50자, locationDescription은 200자로
제한하고, BoothBulkCreateRequestDto의 boothCodes 각 요소에도 `@Size`(max = 30)을 적용하세요.

In `@BE/src/main/java/com/min/edu/booth/service/BoothService.java`:
- Around line 190-202: Validate status transitions in updateStatus or
Booth.changeStatus before persisting the requested status: reject ASSIGNED when
assignedOrganizationId is null, and reject AVAILABLE when assignedOrganizationId
is non-null. Preserve valid transitions and add a TODO if this is intentionally
temporary pending BoothApplication integration.
- Around line 378-384: Update normalizeKeyword to escape user-supplied LIKE
wildcard characters using the query’s escape character, including the escape
character itself, before trimming, lowercasing, and adding wildcards; use
Locale.ROOT for deterministic lowercasing. Update each keyword LIKE predicate in
the relevant JPQL query to include ESCAPE '!' so the escaped values are treated
literally.
- Around line 103-113: Update BoothService.createBulk to replace the per-code
existsByEventIdAndBoothCode loop with one repository query that retrieves
existing booth codes using eventId and an IN filter over boothCodes, then reject
when any conflict is found while preserving duplicate-request validation. Add an
appropriate `@Size`(max = ...) constraint to BoothBulkCreateRequestDto.boothCodes
to enforce the bulk request limit.
- Around line 300-318: Update BoothService.listPublic to apply an explicit
allowlist of publicly visible booth statuses through a dedicated repository
query or search condition, excluding APPLICATION_PENDING and UNAVAILABLE. Ensure
the public response path exposes only allowed statuses and does not return
price, description, or exhibitionContent; keep authenticated/internal listing
behavior unchanged.
- Around line 224-235: updateIntro에서 request의 representativeFileId를 저장하기 전에 해당
파일이 요청 주체 또는 배정 조직의 소유인지 검증하는 로직을 추가하고, 소유권 검증에 실패하면 기존 비즈니스 예외 방식으로 요청을 거부하세요.
파일 ID를 검증 없이 저장하는 경로를 제거하고, 파일 정책에 사용하는 기존 검증 메서드와 오류 코드를 재사용하세요.

In `@BE/src/main/java/com/min/edu/common/exception/GlobalErrorCode.java`:
- Line 68: Update BOOTH_CODE_ALREADY_EXISTS in GlobalErrorCode to use
HttpStatus.CONFLICT and the matching BOOTH_409_001 code, keeping its
duplicate-booth message unchanged. Search frontend handling for BOOTH_400_001
and update that branch to recognize the new 409 code if it exists.
- Line 69: Update the BOOTH_DELETE_NOT_ALLOWED message in GlobalErrorCode to
state that only booths with AVAILABLE status can be deleted, replacing the
wording about application or assignment history while preserving the existing
error code and status.

In
`@BE/src/main/java/com/min/edu/recruitment/service/BoothRecruitmentService.java`:
- Around line 122-124: Update the end-time validation in BoothRecruitmentService
around validatePeriod and recruitment.updateEndAt so an OPEN recruitment accepts
only an end time strictly after the current time; reject past or equal-to-now
values before persisting, while leaving immediate closure to the existing close
API.
- Around line 114-116: In updateEndAt, call requireEventManager(eventId, member)
before getByEventIdOrThrow(eventId), so authorization is evaluated before
recruitment lookup and unauthorized callers receive the same response regardless
of event existence.

In `@FE/src/components/BoothManagementPanel.jsx`:
- Around line 50-57: Validate all numeric form fields in BoothManagementPanel
before constructing the submission payload, including widthMeter, depthMeter,
areaSqm, and price; reject non-empty values that convert to NaN or are otherwise
invalid, such as comma-formatted input. Display a user-facing message
identifying the invalid field and stop submission, while preserving null
handling for intentionally empty optional dimension fields.
- Around line 191-216: Update the event-change and booth-loading flows in
BoothManagementPanel so a failed load clears loadedEventId instead of retaining
the previous event, and reset editingBoothId, introBoothId, and qrBoothId
whenever eventId changes. Update the registration, search, pagination, edit,
delete, intro, and QR action paths to use the current eventId directly rather
than stale loadedEventId or prior-event booth state.

In `@FE/src/components/RecruitmentManagementPanel.jsx`:
- Around line 84-93: Update the RecruitmentManagementPanel request flow around
loadRecruitment and runAction to invalidate prior requests when eventId changes
or is cleared, using a useRef request version or AbortController. Only apply
success, failure, and completion state updates when the request still belongs to
the current event/version; when eventId is cleared, invalidate pending requests
and reset loading as well.

In `@FE/src/pages/OrganizerAdmin.jsx`:
- Around line 46-58: Update the organization selection logic in the
managedOrganizations useEffect to validate the existing organizationId against
the loaded list, not only when it is empty. If the current ID is absent from the
user’s organizations and the list is non-empty, replace it with the first
organization’s ID; preserve valid selections and the existing empty-list
behavior.

In `@FE/src/pages/RecruitmentDetail.jsx`:
- Around line 75-77: Update the booth-loading flow in RecruitmentDetail around
listPublicBooths so it tracks the response page metadata (page and last or
totalPages) alongside content. When the response is not the final page, provide
pagination or a “load more” action that requests subsequent pages and appends
their booths to the existing list, while retaining the current page-size limit
instead of fetching an unlimited result.

---

Nitpick comments:
In `@BE/src/main/java/com/min/edu/booth/domain/Booth.java`:
- Around line 121-191: Reduce positional-argument risk in Booth.create and
Booth.updateDetails by grouping the related same-type fields into dedicated
value objects, such as records for location, dimensions, and equipment, and
update the BoothService.create, createBulk, and update callers to pass those
objects. Preserve the existing field assignments and behavior while removing the
long repeated parameter lists.

In `@BE/src/main/java/com/min/edu/booth/dto/BoothIntroUpdateRequestDto.java`:
- Around line 18-20: Update BoothIntroUpdateRequestDto by adding appropriate
maximum-length validation constraints to description and exhibitionContent,
matching the existing validation style and limits used for displayName and
shortIntro. Keep the fields’ existing behavior unchanged apart from enforcing
these length limits.

In `@BE/src/main/java/com/min/edu/booth/dto/BoothResponseDto.java`:
- Around line 41-42: Remove qrToken and qrIssuedAt from BoothResponseDto, and
introduce or reuse BoothQrResponseDto as the only DTO exposing those QR fields.
Keep BoothPublicResponseDto free of QR data and update QR-specific response
mappings or endpoints to return BoothQrResponseDto while preserving existing
non-QR response behavior.

In `@BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java`:
- Around line 22-39: Update the BoothRepository.search query strategy for
scalable keyword filtering: replace the organization-name EXISTS condition with
a LEFT JOIN to Organization while preserving the existing filters and results,
add the recommended composite indexes for event/status and
event/floorName/zoneName, and use PostgreSQL pg_trgm GIN indexes or a dedicated
searchable column if keyword search is a core requirement. Keep the named
parameter binding unchanged.

In `@BE/src/main/java/com/min/edu/booth/service/BoothService.java`:
- Around line 216-222: Update BoothService.getDetail to allow assigned
organization members as well as event managers, matching the authorization used
by updateIntro and getQr, or provide an equivalent organization-member detail
path. Because toResponse includes qrToken, ensure the expanded-access response
excludes or safely handles that field according to the intended permission
boundary.
- Around line 462-475: Update BoothService.fromJson to log parsing failures
before returning an empty list: add the Lombok `@Slf4j` logger and call log.warn
with descriptive context and the caught JacksonException in the catch block.
Preserve the existing empty-list fallback for invalid JSON and blank or null
input.

In `@BE/src/main/java/com/min/edu/common/config/SecurityConfig.java`:
- Around line 68-69: In BoothService, add a shared guard at the start of
requireEventManager and requireAssignedOrganizationMemberOrEventManager that
throws BusinessException(GlobalErrorCode.UNAUTHORIZED) when member is null,
before any member dereference or authorization checks.

In `@BE/src/test/java/com/min/edu/booth/controller/BoothControllerTest.java`:
- Around line 164-171: Update the assignToExhibitor method to explicitly flush
the EntityManager before executing the JPQL bulk UPDATE, ensuring
MockMvc-created Booth entities are inserted before the update runs. Keep the
existing parameters, executeUpdate call, and entityManager.clear() behavior
unchanged.
- Around line 218-222: Replace the double-brace LinkedHashMap construction in
the bulk request test with Map.of or a focused request-body helper. Extend
BoothControllerTest with unauthenticated coverage for
/events/{eventId}/booths/public, including valid retrieval and validation
failures for invalid page and size parameters.

In
`@BE/src/test/java/com/min/edu/recruitment/controller/BoothRecruitmentControllerTest.java`:
- Around line 340-361: Update the test method OPEN_상태에서는_모집_종료일만_수정할_수_있다 to
assert that the response recruitmentEndAt matches newEnd, in addition to the
existing success and OPEN status checks. If the response is insufficient, clear
the persistence context and verify the stored value through a managed retrieval.

In `@FE/src/api/boothApi.js`:
- Around line 9-25: Extract the shared parameter filtering and URLSearchParams
construction from listBooths and listPublicBooths into a reusable helper, then
use that helper in both functions while preserving the current handling of empty
and nullish 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: e76018e7-e9d8-4dc6-8610-c5fcc1fba53b

📥 Commits

Reviewing files that changed from the base of the PR and between 3962680 and 8bf7fec.

⛔ Files ignored due to path filters (1)
  • FE/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (33)
  • BE/src/main/java/com/min/edu/booth/controller/BoothController.java
  • BE/src/main/java/com/min/edu/booth/domain/Booth.java
  • BE/src/main/java/com/min/edu/booth/domain/BoothRecruitment.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothBulkCreateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothCreateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothIntroUpdateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothPageResponse.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothPublicPageResponse.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothPublicResponseDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothQrResponseDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothResponseDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothStatusUpdateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothUpdateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java
  • BE/src/main/java/com/min/edu/booth/service/BoothService.java
  • BE/src/main/java/com/min/edu/booth/support/BoothQrTokenGenerator.java
  • BE/src/main/java/com/min/edu/common/config/SecurityConfig.java
  • BE/src/main/java/com/min/edu/common/exception/GlobalErrorCode.java
  • BE/src/main/java/com/min/edu/organization/repository/OrganizationMemberRepository.java
  • BE/src/main/java/com/min/edu/recruitment/controller/BoothRecruitmentController.java
  • BE/src/main/java/com/min/edu/recruitment/dto/BoothRecruitmentEndAtUpdateRequestDto.java
  • BE/src/main/java/com/min/edu/recruitment/service/BoothRecruitmentService.java
  • BE/src/test/java/com/min/edu/booth/controller/BoothControllerTest.java
  • BE/src/test/java/com/min/edu/recruitment/controller/BoothRecruitmentControllerTest.java
  • BE/src/test/java/com/min/edu/recruitment/scheduler/BoothRecruitmentStatusSchedulerTest.java
  • FE/package.json
  • FE/src/api/boothApi.js
  • FE/src/api/recruitmentApi.js
  • FE/src/components/BoothManagementPanel.jsx
  • FE/src/components/RecruitmentManagementPanel.jsx
  • FE/src/pages/Home.jsx
  • FE/src/pages/OrganizerAdmin.jsx
  • FE/src/pages/RecruitmentDetail.jsx

Comment thread BE/src/main/java/com/min/edu/booth/dto/BoothBulkCreateRequestDto.java Outdated
Comment thread BE/src/main/java/com/min/edu/booth/dto/BoothCreateRequestDto.java
Comment on lines +17 to +50
@NotBlank
private String boothCode;

@NotBlank
private String boothType;

private String floorName;

private String zoneName;

private String locationDescription;

@DecimalMin(value = "0", inclusive = true)
private BigDecimal widthMeter;

@DecimalMin(value = "0", inclusive = true)
private BigDecimal depthMeter;

@DecimalMin(value = "0", inclusive = true)
private BigDecimal areaSqm;

private List<String> basicEquipment;

private boolean electricityAvailable;

private boolean waterAvailable;

private boolean drainageAvailable;

private boolean internetAvailable;

@NotNull
@DecimalMin(value = "0", inclusive = true)
private BigDecimal price;

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

DB 제약과 요청 검증을 일치시키세요.

@NotBlank와 @DecimalMin만으로는 문자열 길이와 소수 정밀도를 제한하지 않습니다. 예를 들어 31자 boothCode 또는 NUMERIC(6,2) 범위를 넘는 규격 값이 요청 검증을 통과합니다. DB 저장 시 값이 반올림되거나 실패할 수 있습니다.

또한 BoothService.update는 모든 DataIntegrityViolationException을 BOOTH_CODE_ALREADY_EXISTS로 변환합니다. 길이·정밀도 오류도 중복 코드 오류로 잘못 응답됩니다. DTO에 @Size와 @Digits를 추가하고, 서비스에서는 유일성 제약 위반만 중복 코드 오류로 변환하세요.

수정 예시
 import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.Digits;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;

     `@NotBlank`
+    `@Size`(max = 30)
     private String boothCode;

     `@NotBlank`
+    `@Size`(max = 50)
     private String boothType;

+    `@Size`(max = 50)
     private String floorName;

+    `@Size`(max = 50)
     private String zoneName;

+    `@Size`(max = 200)
     private String locationDescription;

     `@DecimalMin`(value = "0", inclusive = true)
+    `@Digits`(integer = 4, fraction = 2)
     private BigDecimal widthMeter;

     `@DecimalMin`(value = "0", inclusive = true)
+    `@Digits`(integer = 4, fraction = 2)
     private BigDecimal depthMeter;

     `@DecimalMin`(value = "0", inclusive = true)
+    `@Digits`(integer = 6, fraction = 2)
     private BigDecimal areaSqm;

     `@NotNull`
     `@DecimalMin`(value = "0", inclusive = true)
+    `@Digits`(integer = 12, fraction = 0)
     private BigDecimal price;

근거: BE/src/main/resources/db/migration/V1__init_schema.sql의 열 제약과 BE/src/main/java/com/min/edu/booth/service/BoothService.java의 예외 변환 경로입니다.

Comment thread BE/src/main/java/com/min/edu/booth/service/BoothService.java
Comment thread BE/src/main/java/com/min/edu/booth/service/BoothService.java
Comment thread FE/src/components/BoothManagementPanel.jsx Outdated
Comment thread FE/src/components/BoothManagementPanel.jsx
Comment thread FE/src/components/RecruitmentManagementPanel.jsx
Comment thread FE/src/pages/OrganizerAdmin.jsx Outdated
Comment thread FE/src/pages/RecruitmentDetail.jsx Outdated
- 부스 등록/수정 DTO에 문자열 길이·소수 정밀도 검증과 일괄등록 개수 상한 추가
- 일괄등록 중복검사를 N+1 쿼리에서 단일 IN 쿼리로 변경
- 부스 상태 전이 검증 추가 (배정 없이 ASSIGNED로, 배정된 부스를 AVAILABLE로 변경 차단)
- 공개 부스 목록에 상태 allowlist 적용 (심사중/사용불가 부스 비공개)
- LIKE 검색 와일드카드 이스케이프 및 로케일 고정
- 부스 번호 중복 에러를 409(Conflict)로, 삭제 제한 메시지를 실제 조건에 맞게 수정
- 모집 공고 서비스 전 메서드에서 권한 검사를 조회보다 먼저 수행하도록 순서 변경,
  모집 종료일을 과거로 설정하는 것 차단
- 배정 부스 소개 수정 시 대표 이미지 파일 소유권 검증 추가
- BoothManagementPanel: 행사 전환 시 이전 행사에 요청이 나가던 버그 수정,
  숫자 입력값 검증 추가
- RecruitmentManagementPanel: 행사 빠른 전환 시 늦게 도착한 이전 응답이
  화면을 덮어쓰지 않도록 요청 버전 가드 추가
- OrganizerAdmin: localStorage에 남은 organizationId가 현재 계정 소속이
  아니면 자동 교체
- RecruitmentDetail: 공개 부스 목록에 페이지네이션(더 보기) 추가

@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

Caution

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

⚠️ Outside diff range comments (2)
FE/src/pages/OrganizerAdmin.jsx (1)

257-260: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

조직 전환 뒤 이전 행사 목록 응답을 무시하세요.

조직 A의 organizationList 요청이 진행 중일 때 조직 B를 선택하면, 늦게 도착한 A 응답이 managedEvents를 다시 설정할 수 있습니다. 이전 effect는 캡처한 빈 selectedEventId를 기준으로 A의 첫 행사를 자동 선택할 수도 있습니다.

그 결과 선택 조직은 B인데 selectedEventId는 A가 됩니다. RecruitmentManagementPanel과 BoothManagementPanel은 이 ID로 요청을 전송합니다.

행사 목록 effect에 취소 플래그 또는 요청 버전을 추가하세요. 응답 반영과 자동 행사 선택은 최신 organizationId일 때만 실행하세요. 목록 조회 시작 시 이전 목록과 오류 상태도 초기화하세요.

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/OrganizerAdmin.jsx` around lines 257 - 260, Update the 행사 목록
effect that loads managedEvents for organizationId to cancel or version each
request, and only apply its response or automatic first-event selection when it
still belongs to the latest organizationId. When a new organization is selected
in the onChange handler, clear the previous event list and error state before
loading the new list, while preserving the existing selectedEventId reset.

Source: Path instructions

FE/src/components/BoothManagementPanel.jsx (1)

220-246: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

이전 행사 요청의 결과를 현재 행사 화면에 반영하지 마세요.

Line 221에서 행사 A의 목록 요청이 진행 중일 때 행사 B를 선택하면, 늦게 도착한 A 응답이 pageResult를 B 화면에 저장할 수 있습니다. 이후 사용자는 A의 부스를 보지만 상태 변경·수정·삭제 요청은 현재 eventId인 B로 전송합니다.

loadBooths에 요청 버전 또는 AbortController를 적용하세요. setPageResult, setError, setLoading은 최신 행사 요청일 때만 실행하세요. runAction의 성공 후 refresh()도 같은 버전을 확인해야 합니다.

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/components/BoothManagementPanel.jsx` around lines 220 - 246,
loadBooths의 행사별 요청에 요청 버전 또는 AbortController를 적용해 이전 행사 요청이 완료되어도 현재 행사 상태를 갱신하지
않도록 수정하세요. setPageResult, setError, setLoading은 최신 요청일 때만 실행되게 하고, runAction의 성공
후 refresh()도 동일한 최신 요청 기준을 확인하도록 연결하세요.

Source: Path instructions

♻️ Duplicate comments (1)
FE/src/components/RecruitmentManagementPanel.jsx (1)

90-99: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

행사 전환 시 submitting 상태를 해제하세요.

행사 A에서 작업을 시작한 뒤 행사 B로 전환하면 requestVersionRef.current가 증가합니다. 이전 작업의 finally는 버전 불일치로 setSubmitting(false)를 실행하지 않습니다. 현재 effect도 submitting을 초기화하지 않습니다.

그 결과 B 화면의 버튼이 계속 비활성화되고, runAction은 Line 110의 조건에서 즉시 반환합니다. 행사 변경 effect에서 요청 버전을 증가시킨 직후 setSubmitting(false)를 실행하세요.

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

Also applies to: 112-126, 144-159

🤖 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/components/RecruitmentManagementPanel.jsx` around lines 90 - 99,
Update the event-change useEffect to call setSubmitting(false) immediately after
incrementing requestVersionRef.current, ensuring switching events resets the
submission state even when the previous request’s finally block is ignored due
to a version mismatch.

Source: Path instructions

🧹 Nitpick comments (1)
BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java (1)

34-42: 🚀 Performance & Scalability | 🔵 Trivial

키워드 검색의 실행 계획을 확인해 주세요.

Line [34-42]는 세 컬럼에 LOWER(...) LIKE :keyword를 적용하고 조직명에는 EXISTS 서브쿼리를 사용합니다. 서비스가 contains 검색을 위해 %...%를 전달하면 일반 인덱스 사용이 제한됩니다. Page 조회는 콘텐츠 조회 외에 count 쿼리도 수행하므로 대규모 행사에서 전체 스캔 비용이 커질 수 있습니다. 콘텐츠 쿼리와 count 쿼리의 실행 계획을 확인하고, 필요하면 대소문자 무시 인덱스나 전용 검색 인덱스를 사용하세요.

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 `@BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java` around
lines 34 - 42, BoothRepository의 키워드 검색 쿼리에 대해 콘텐츠 조회와 Page count 쿼리 양쪽의 실행 계획을
확인하고, 대규모 데이터에서 전체 스캔이 발생하면 LOWER 대상 컬럼에 대소문자 무시 인덱스 또는 전용 검색 인덱스를 적용하세요.
boothCode, displayName, Organization.name 검색과 assignedOrganizationId 연계를 각각
검토하고, contains 검색의 `%keyword%` 동작과 기존 필터 결과는 유지하세요.

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/RecruitmentDetail.jsx`:
- Around line 104-119: Update the booth-loading flow around loadMoreBooths to
invalidate requests from previous events: reset booths, boothPage, boothHasMore,
and loadingMoreBooths when recruitment.eventId changes, track a request
generation for each event, and apply response or error state updates only when
the request generation still matches the current generation.

---

Outside diff comments:
In `@FE/src/components/BoothManagementPanel.jsx`:
- Around line 220-246: loadBooths의 행사별 요청에 요청 버전 또는 AbortController를 적용해 이전 행사
요청이 완료되어도 현재 행사 상태를 갱신하지 않도록 수정하세요. setPageResult, setError, setLoading은 최신 요청일
때만 실행되게 하고, runAction의 성공 후 refresh()도 동일한 최신 요청 기준을 확인하도록 연결하세요.

In `@FE/src/pages/OrganizerAdmin.jsx`:
- Around line 257-260: Update the 행사 목록 effect that loads managedEvents for
organizationId to cancel or version each request, and only apply its response or
automatic first-event selection when it still belongs to the latest
organizationId. When a new organization is selected in the onChange handler,
clear the previous event list and error state before loading the new list, while
preserving the existing selectedEventId reset.

---

Duplicate comments:
In `@FE/src/components/RecruitmentManagementPanel.jsx`:
- Around line 90-99: Update the event-change useEffect to call
setSubmitting(false) immediately after incrementing requestVersionRef.current,
ensuring switching events resets the submission state even when the previous
request’s finally block is ignored due to a version mismatch.

---

Nitpick comments:
In `@BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java`:
- Around line 34-42: BoothRepository의 키워드 검색 쿼리에 대해 콘텐츠 조회와 Page count 쿼리 양쪽의 실행
계획을 확인하고, 대규모 데이터에서 전체 스캔이 발생하면 LOWER 대상 컬럼에 대소문자 무시 인덱스 또는 전용 검색 인덱스를 적용하세요.
boothCode, displayName, Organization.name 검색과 assignedOrganizationId 연계를 각각
검토하고, contains 검색의 `%keyword%` 동작과 기존 필터 결과는 유지하세요.
🪄 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: 1aa764a9-18de-4935-b505-af28f4c537b8

📥 Commits

Reviewing files that changed from the base of the PR and between 8bf7fec and 29d58e3.

📒 Files selected for processing (13)
  • BE/src/main/java/com/min/edu/booth/dto/BoothBulkCreateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothCreateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothUpdateRequestDto.java
  • BE/src/main/java/com/min/edu/booth/repository/BoothRepository.java
  • BE/src/main/java/com/min/edu/booth/service/BoothService.java
  • BE/src/main/java/com/min/edu/common/exception/GlobalErrorCode.java
  • BE/src/main/java/com/min/edu/file/service/FileService.java
  • BE/src/main/java/com/min/edu/recruitment/service/BoothRecruitmentService.java
  • BE/src/test/java/com/min/edu/booth/controller/BoothControllerTest.java
  • FE/src/components/BoothManagementPanel.jsx
  • FE/src/components/RecruitmentManagementPanel.jsx
  • FE/src/pages/OrganizerAdmin.jsx
  • FE/src/pages/RecruitmentDetail.jsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • BE/src/test/java/com/min/edu/booth/controller/BoothControllerTest.java
  • BE/src/main/java/com/min/edu/booth/dto/BoothBulkCreateRequestDto.java
  • BE/src/main/java/com/min/edu/common/exception/GlobalErrorCode.java
  • BE/src/main/java/com/min/edu/recruitment/service/BoothRecruitmentService.java
  • BE/src/main/java/com/min/edu/booth/service/BoothService.java

Comment thread FE/src/pages/RecruitmentDetail.jsx
행사를 빠르게 전환하면 이전 행사의 loadMoreBooths 응답이 새 행사 화면의
booths/boothPage/boothHasMore를 덮어쓸 수 있었다. 부스 목록 로딩과
더 보기 요청을 같은 세대(generation) 참조로 묶어, 오래된 응답은 무시하도록 했다.
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.

2 participants