Skip to content

MSG-500 feat: 관리자가 신청을 심사해 승인하면 행사가 지도에 실린다 - #245

Merged
s13121312 merged 36 commits into
developfrom
feature/MSG-500-admin-review
Aug 30, 2026
Merged

MSG-500 feat: 관리자가 신청을 심사해 승인하면 행사가 지도에 실린다#245
s13121312 merged 36 commits into
developfrom
feature/MSG-500-admin-review

Conversation

@s13121312

@s13121312 s13121312 commented Aug 30, 2026

Copy link
Copy Markdown
Member

🎫 관련 티켓

작업 내용

  • 관리자 심사 큐·상세 조회: GET /api/admin/event-submissions(상태 필터 + 탭 건수 3종), 상세는 위치 사각형·노출 영역1·신청 계정 정보에 참여형 전용 재료(참여 방식·부모 이벤트)까지 싣습니다
  • 승인: 유형별 분기 — 지역축제는 축제 미션, 팝업스토어는 팝업 미션으로 등재해 지도 홈 칩에 즉시 노출(1시간 미션 스냅숏 캐시를 커밋 후 무효화2), 이벤트 참여형은 부모 회차 아래 행사 위치로 전개해 참여 속성 6종(운영 주체·소개·공개 기간·참여 방식·커버 이미지)을 함께 노출합니다. 승인 번호 APR-{연도}-{4자리} 부여
  • 반려: 항목 코드(PERIOD·AREA·IMAGE·INFO) 1개 이상 + 자유 서술 필수, 이력 테이블 단일 저장
  • 승인 행사 목록·노출 중지: GET /api/admin/events(KST 파생 탭), 중지는 hidden_at 숨김 + 사용자 대면 전 경로 차단(칩·격자 선택·영상 피드·직링크·댓글·도움돼요) + 부모 회차 노출 영역 재계산 + 사유 메일. 이미 받은 스탬프·뱃지는 회수하지 않습니다
  • 아이디(공식 이메일) 변경 요청의 관리자 심사(SRS FR-USER-16 배정 편입): 검토 시점 가드(1429)·이메일 단일 컬럼 교체·새 이메일 통지
  • 스키마 V51(심사 흔적·숨김 컬럼·참여 속성·처리 흔적), 에러 코드 1345013456·14271429 신설
  • 실측: 79 files changed, +7,078 / −84. 전수 테스트 2,971건 green, 신규 검증 91건(// 검증: FR-... 주석으로 RTM 연결, 공백 0건)

🤔 고민한 내용

  • 승인 반영의 유형별 분기: 2026-08-29 정민 확정(PRD v2.2)에 따라 축제·팝업은 행사 도메인이 아니라 미션으로 등재합니다. publish()의 switch에 default를 두지 않아, 유형이 늘면 컴파일 에러로 분기 누락이 드러납니다

  • 노출 중지 = 삭제가 아니라 숨김(hidden_at): 산출물 삭제는 스탬프 FK와 비회수 원칙에 충돌하고, 기간 소급은 저장값이 사실과 달라져 기각했습니다. 뱃지 지표·진행도에는 일부러 필터를 걸지 않았습니다(완료 기록 보존)

  • 동시성: 같은 회차를 노리는 동시 승인은 회차 비관 잠금으로 직렬화(지연 유니크 제약의 커밋 시점 500 방지), 이메일 승인은 users 행 선잠금으로 계정 삭제의 FK CASCADE와 잠금 순서를 통일(AB-BA 데드락 차단), 같은 요청 동시 재승인은 전이 UPDATE를 선검사 앞에 둬 1428로 결정적 수렴

  • users.email은 단일 컬럼 UPDATE: 이 프로젝트는 @DynamicUpdate를 안 써 관리 엔티티 flush가 전 컬럼 UPDATE를 냅니다. 동시 비밀번호 변경을 되덮지 않도록 @Modifying JPQL로 email만 갱신합니다

    update users set email=? where id=?
  • 노출 영역 산술 공용화: 승인(합집합 확장)·중지(남은 가시 위치 재계산)·재시드(시드 ∪ 가시 참여 격자)가 EventExposureArea 한 곳의 산술을 입력만 달리해 씁니다. 재시드 합집합을 참여 위치로 한정해 시드 축소 시 옛 격자로 부풀지 않습니다

  • 미채택 판정 2건: 미션 업로드의 잠금 없는 숨김 검사(첫 완료는 스탬프 보존 확인이 같은 트랜잭션에서 롤백시켜 실해가 없음), 멱등 재시도의 숨김 게이트(새 저장 없는 과거 사실 재응답이라 게이트를 걸면 멱등 계약이 깨짐) — 근거는 스펙 작업 로그 참조

👀 리뷰 포인트

  • 숨김 소비 전수 목록(미션 8곳 + 행사 7곳)에서 빠진 사용자 대면 경로가 없는지 — 일부러 안 건 3곳(진행도·방문 스팟·뱃지 집계)의 판단 포함
  • 배포 전 선행: S3 event-locations/org-submission/* 공개 읽기 정책이 dev·prod 미적용입니다(missions/org-submission/은 기존 missions/* 정책이 커버). 런북은 .claude/docs/deploy.md 4번
  • SRS ID 재번호: MSG-502가 develop에 FR-EVENT-17을 선점해 이 브랜치의 중지→18, 목록→19로 옮겼습니다

https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV

Footnotes

  1. 노출 영역: 지도 뷰포트와의 겹침 판정에 쓰는 격자 경계 사각형. 행사 위치 사각형들을 전부 감싸는 범위로, 상단 행사 칩이 어느 지역 화면에서 보일지를 정합니다.

  2. 커밋 후 무효화: 트랜잭션이 실제로 커밋된 뒤에만 캐시를 비우는 방식. 커밋 전에 비우면 아직 저장 안 된 상태를 다른 요청이 읽어 다시 캐시에 담아버립니다.

13450~13454는 스펙 표, 13455·13456은 목록 검증용. user 대역 1424·1425를 event 컨트롤러가 내지 않기 위한 신설이고 13440대는 MSG-502 예약이라 피했다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
관리자 상세와 신청자 상세가 같은 영역 그림을 보게 하기 위한 이동이고 로직 변경은 없다. 읽기 경로의 재검증(81칸 상한)은 제거됐다 — 저장 데이터는 접수 검증을 이미 통과했다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
13450 상수명 정정(SUBMISSION_STATUS_NOT_REVIEWABLE), 13455·13456 행 추가, M5의 1424·1425 재사용 근거 명시.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
FR-EVENT-17·18 신설, FR-EVENT-15는 유형별 분기 확정으로 재작성. M4 테스트가 인용할 ID를 소급 주석 없이 확보한다(MSG-390 교훈).

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
시더의 KST 변환·대표 격자 규칙을 public 승격해 재사용하고(복제 0), 무효화는 refreshLock 안에서 홀더를 비워 옛 결과 되채움을 차단한다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
셀 전개를 EventSubmissionCells 한 곳으로 모아 상세 칸 수와 승인 판정 격자가 같은 산술을 쓴다. 이미지 복사는 보상 등록이 복사보다 선행한다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
승인은 선검사, 전이와 승인 번호 한 문장 UPDATE, 재로드 후 등재 순서의 한 트랜잭션이다. 검증 19건.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
hidden_at IS NULL 술어로 첫 중지 시각을 보존하고, 활성·스탬프 판정·격자 역조회에 숨김 필터를 건다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
피드는 빈 목록이 아니라 미션 상세와 같은 404(12404)로 수렴한다. 진행도·방문 스팟·뱃지 지표에는 필터를 걸지 않는다(완료 기록 비회수).

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
탭 상태는 KST 오늘로 파생하고, 중지는 기록과 숨김 한 트랜잭션 뒤 커밋 성공 시에만 사유 메일을 보낸다(실패 시 emailSent=false).

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
트랜잭션 격리 클래스에선 롤백을 관찰할 수 없어 비트랜잭션 클래스에 편입했다. 시드 헬퍼를 픽스처로 추출.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
hide의 무효화 한 줄을 지우면 반드시 실패하는 형태다. 승인 쪽 기존 검증과 짝을 이뤄 D-12가 양방향으로 고정됐다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
전이 술어에 접수 시각 일치를 포함해 재제출 경합을 막고, 이메일 교체는 단일 컬럼 UPDATE라 동시 갱신을 되덮지 않는다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
승인은 전이와 이메일 교체 한 트랜잭션이고, 통지는 새 이메일로 커밋 후 발송한다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
검토 시점 가드와 동시 승인 경합, 커밋된 비밀번호 변경 생존까지 계약 단위로 덮는다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
승인(요청 행, users 행)과 탈퇴(users 삭제, CASCADE 요청 행)의 AB-BA 교차를 닫는다. 문장 순서 교체안은 1429/1409 우선순위가 흔들려 기각.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
작업 로그에 생성 SQL 실측, 예외 흐름 16행 표, afterCommit·잠금 순서·시퀀스 갭 등 런타임 동작과 MSG-502 인계 2건을 기록했다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
FR-USER-16과 FR-EVENT-18은 구현됨, FR-EVENT-15와 17은 참여형 잔여로 진행 중 유지.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
이메일 선검사를 전이 UPDATE 뒤로 옮겨 재승인은 0행 분기의 1428로 결정된다. 1409 거절 시 전이가 함께 롤백됨을 실커밋 테스트로 고정했다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
충돌 7파일 해소. SRS는 MSG-502가 선점한 FR-EVENT-17에 맞춰 중지를 18, 목록을 19로 재번호하고 테스트 주석과 문서 참조를 함께 옮겼다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
승인·중지·재시드가 같은 산술에 입력만 달리 넣는다. 재시드 합집합은 참여 위치 가시 격자로 한정해 시드 축소 시 옛 격자로 부풀지 않는다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
회차 잠금 아래 두 방향 겹침 검사 후 위치·참여 속성·커버 이미지를 반영하고 노출 영역을 넓힌다. 심사 상세에 참여 방식과 부모 이벤트를 싣는다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
@claude

claude Bot commented Aug 30, 2026

Copy link
Copy Markdown

Claude finished @s13121312's task in 4m 0s —— View job


리뷰 완료

확인 범위: origin/develop 대비 마이그레이션(V51)·developCode 대역(1345013456, 14271429) 충돌 여부, docs/spec/MSG-500.md 전체(913줄, 작업 로그 포함) 대비 구현 일치 여부(승인 유형별 분기, 참여형 전개, 노출 영역 산술 공용화, 숨김 소비 전수, 트랜잭션 경계), 관리자 승인·중지·아이디 변경 심사의 동시성(비관 잠금 순서, 조건부 UPDATE, 검토 시점 가드), 이메일 단일 컬럼 UPDATE, PR 본문 주장(S3 버킷 정책 미적용 문서화, RTM/SRS 재번호).

발견

라벨 위치 요약
P1 docs/rtm.md:204 CI "RTM 신선도 검사" 레드 — EventSeederTestFR-EVENT-15 검증 마커가 커밋된 rtm.md에 반영 안 됨(재생성 스크립트 미실행)

코드 레벨(승인/반려/중지/이메일 심사 서비스, 리포지토리 쿼리, 엔티티, 마이그레이션, 잠금 순서, 숨김 필터 8+7곳)은 스펙·작업 로그의 서술과 실제 구현이 정확히 일치함을 확인했고, 병렬 브랜치 경합(마이그레이션 번호·developCode 대역)도 충돌 없음을 확인했습니다. 인라인 코멘트는 위 1건입니다.

Comment thread docs/rtm.md Outdated
상호 배제는 양쪽이 다 잠가야 성립한다. 기동 시딩이 계약 밖이면 스테일 합집합이 방금 승인된 확장을 되쓰거나 시드 격자 삽입이 커밋 시점 500이 된다.

Claude-Session: https://claude.ai/code/session_01W1NYGqr7zNx6u2iNFvLodV
@github-actions

Copy link
Copy Markdown

테스트 커버리지

Overall Project 96.12% -0.23% 🍏
Files changed 96.46% 🍏

File Coverage
AdminOrgAccountController.java 100% 🍏
EventLocation.java 100% 🍏
EventSubmissionRejectRequestDto.java 100% 🍏
AdminEventSubmissionListResponseDto.java 100% 🍏
AdminEventUnpublishRequestDto.java 100% 🍏
EventSubmissionApproveResponseDto.java 100% 🍏
AdminApprovedEventListResponseDto.java 100% 🍏
AdminEventSubmissionDetailResponseDto.java 100% 🍏
AdminEventUnpublishResponseDto.java 100% 🍏
AdminApprovedEventItemResponseDto.java 100% 🍏
AdminEventSubmissionItemResponseDto.java 100% 🍏
Mission.java 100% 🍏
MissionRegistrationService.java 100% 🍏
MissionQueryService.java 100% 🍏
EventExposureArea.java 100% 🍏
EventErrorCode.java 100% 🍏
EventLocationResponseDto.java 100% 🍏
UserErrorCode.java 100% 🍏
EventSubmissionLocationView.java 100% 🍏
EventSubmissionCells.java 100% 🍏
AdminEventSubmissionController.java 100% 🍏
AdminApprovedEventController.java 100% 🍏
EventSubmission.java 100% 🍏
EventSubmissionStatusHistory.java 100% 🍏
MissionRepresentativeGrids.java 100% 🍏
EmailChangeApproveResponseDto.java 100% 🍏
EmailChangeRejectRequestDto.java 100% 🍏
EmailChangeApproveRequestDto.java 100% 🍏
AdminEmailChangeRequestListResponseDto.java 100% 🍏
AdminEmailChangeRequestItemResponseDto.java 100% 🍏
EventQueryServiceImpl.java 99.56% -0.11% 🍏
VideoServiceImpl.java 98.13% 🍏
EventSubmissionServiceImpl.java 97.85% 🍏
MissionVideoServiceImpl.java 97.78% -0.32% 🍏
EventVideoServiceImpl.java 96.84% -1.11%
MissionQueryServiceImpl.java 96.75% 🍏
AdminEventSubmissionService.java 96.73% -3.27% 🍏
EventVideoInteractionServiceImpl.java 96.64% -1.12%
MissionRegistrationServiceImpl.java 96.3% -3.7% 🍏
EventSeeder.java 94.96% 🍏
AdminApprovedEventService.java 94.85% -5.15% 🍏
EventSubmissionImageStore.java 93.26% -0.67% 🍏
AdminEmailChangeRequestService.java 89.25% -10.75% 🍏
FestivalMissionSeeder.java 85.74% 🍏

@s13121312
s13121312 merged commit 6c970e8 into develop Aug 30, 2026
1 check passed
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